프로미스, async / await
2024-05-26 오전 10시 22분
2024-06-26 오전 10시 22분
프로미스

고객이 어떤 가게에서 물건을 주문할 때, 그 물건의 준비가 다되었는지 여부를 확인해야 할 때가 있다.
고객이 요청을 보냈을때, 준비가 완료
되었거나, 실패
되었거나, 대기중
이라는것을 알리는 것이
Promise
라고 할 수 있다. 달리 말하면 비동기적인 함수를 동기적으로 함수로서 구현했다고 생각하면 된다.
const pr = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 1000);
});
//then이 받는 첫번째 인자가 resolve
//catch가 받는 첫번째 인자가 reject
//finally는 받는 인자가 따로 없음
pr.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log('완료');
});
promise
는 콜백함수의 집단이라고 생각하면 이해하기 편하다. 각각 받는 인자가 다르므로
고려하면서 작성하는게 좋다.
프로미스 체이닝
작업1
을 하면 작업2
를, 작업2
를 하면 작업3
을 해야할 경우 어떻게 구현해야할까?
우선 일반 함수로 구현해보자
const f1 = (callback) => {
setTimeout(() => {
console.log('1번 주문 완료');
callback();
}, 1000);
};
const f2 = (callback) => {
setTimeout(() => {
console.log('2번 주문 완료');
callback();
}, 2000);
};
const f3 = (callback) => {
setTimeout(() => {
console.log('3번 주문 완료');
callback();
}, 3000);
};
f1(() => {
f2(() => {
f3(() => {
console.log('완료');
});
});
});
딱봐도 코드의 가독성이 좋지않다. f1 → f2 → f2순으로 콜백이 이루어지면서 콜백 지옥(callback Hell)
이라 칭하는 가독성이 좋지않은 코드가 만들어지는 것이다. 이걸 promise
로 구현해보자.
const f1 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('1번');
}, 1000);
});
};
const f2 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2번');
}, 2000);
});
};
const f3 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('3번');
}, 3000);
});
};
f1()
.then((resolve) => f2(resolve))
.then((resolve) => f3(resolve))
.then((resolve) => console.log(resolve))
.finally(() => {
console.log('done');
});
- f1() 함수가 실행되고
resolve
를 인자로 보낸다.
- f1() 함수 내에서
return new Promise
가 실행되고, 작업이 완료 된 후 resolve
값을 message
에게 콜백
- f1() 함수에서 콘솔 값을 출력하고, f2() 함수로 넘어가 같은 과정이 반복된다.

그림으로 표현한다면 이런 흐름이다. 그렇다면 이번에는 reject
를 추가해보자.
1부터 10의 수를 받고, 7초과의 값이 나오면 reject
가 되게 코드를 구성해보자.
const f1 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject('전송 실패');
resolve('1번');
}, 1000);
});
};
const f2 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject('전송 실패');
resolve('2번');
}, 2000);
});
};
const f3 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject('전송 실패');
resolve('3번');
}, 3000);
});
};
f1()
.then((resolve) => f2(resolve))
.then((resolve) => f3(resolve))
.then((resolve) => console.log(resolve))
.catch(console.log)
.finally(() => {
console.log('done');
});
이렇게 작성할 경우 7이상의 숫자가 나올 경우,
catch
로 값이 넘어가 console.log
에 리턴되어 에러로 처리를 중지한다.
에러 발생시 .then
을 무시하고 .catch
로 넘어가니 이점을 주의하자, 이렇게 여러개의 프로미스를 엮어서
사용하는것을 프로미스 체이닝이라고 한다.
async, await
프로미스 체이닝은 가독성이 좋아지긴 하지만 마찬가지로 개선할 점이 많다. 이를 위해서 나온 문법이
async, await
다.
async function getName() {
return 'Mike';
}
getName().then((name) => {
console.log(name);
});
//mike
async
로 선언된 함수는 promise
로서 동작하게 된다.
내부에 return promise
가 존재한다고 생각하면 되는 것이다.
function getName(name) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(name);
}, 1000);
});
}
async function showName() {
const result = await getName("mike")
console.log(result);
}
console.log('시작')
showName()
//시작
//(1s...)
//mike
await
은 async
내부에서만 사용할 수 잇다.
await
은 작업이 완료될 때 까지 대기 한 후 완료됫을 때 값을 리턴한다.
이 두개를 활용해 더욱 가독성 좋은 코드로 구현할 수 있다.
const f1 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject('전송 실패');
resolve('1번');
}, 1000);
});
};
const f2 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject('전송 실패');
resolve('2번');
}, 2000);
});
};
const f3 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject('전송 실패');
resolve('3번');
}, 3000);
});
};
async function farm() {
const result1 = await f1();
const result2 = await f2(result1);
const result3 = await f3(result2);
console.log(result3);
console.log('완료');
}
farm()
위의 작성된 then
을 활용한 코드를 간단하게 async await
문법으로 다시 구현했다.
다만 이렇게 할 경우, error
의 대한 처리를 할 수 없다. 그럴때 사용하는게 try catch
문법이다.
const f1 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject(new Error('1번 전송 실패'));
resolve('1번 주문완료');
}, 1000);
});
};
const f2 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject(new Error('2번 전송 실패'));
resolve('2번 주문완료');
}, 1000);
});
};
const f3 = (message) => {
console.log(message);
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomReject = Math.floor(Math.random() * 10) + 1;
if (randomReject > 7) reject(new Error('3번 전송 실패'));
resolve('3번 주문완료');
}, 1000);
});
};
async function farm() {
try {
const result = await f1();
const result2 = await f2(result);
const result3 = await f3(result2);
console.log(result3);
} catch (error) {
console.log(error);
}
}
farm();
일반적으로 promise
문법의 경우 async
, await
과 try
,catch
를 같이 사용한다고 한다.
프로미스의 기본구조를 모르면 이해하기 힘드므로 꼭 기억하자.
댓글은 포스팅에 도움이됩니다. 적극적인 의견 감사드립니다.