💌 이 글은 드림코딩의 비동기 3부작 | 콜백 - 프로미스 - async & await 에서 async & await에 대해 다룹니다
async
& await
앞서 콜백 지옥을 해결하기 위해 프로미스를 쓴다고 공부했다. 그렇다면 프로미스는 아무런 문제가 없는 방법일까?
💬 예제 1
function delay(ms) {
return new Promise (resolve => setTimeout(resolve, ms));
}
function getApple() {
return delay(1000)
.then(() => `🍎`);
}
function getBanana() {
return delay(1000)
.then(() => `🍌`);
}
function pickFruits() {
return getApple().then(apple => {
return getBanana().then(banana => `${apple} + ${banana}`);
}); // 🔥 콜백 지옥의 향기...
}
pickFruits().then(console.log); // 🍎 + 🍌
위의 예제코드에서 알 수 있듯 프로미스도 여러번 중첩해서 사용하면 결국 콜백 지옥과 똑같은 문제가 발생한다. 이 문제는 async
와 await
을 통해 해결할 수 있다.
async
& await
를 함께 사용하여 읽고 쓰기 쉬운 비동기 코드를 작성할 수 있다.async
& await
를 사용하면 promise.then
이 거의 필요 없다.async
& await
는 프로미스를 기반으로 한다.async
async
를 통해 프로미스를 좀 더 편하게 사용할 수 있다. 이것은 새로운 것이 아니라, 기존의 프로미스를 베이스로 API를 추가하는 것으로, Syntactic Sugar의 일종이다.
✔ Class 또한 프로토타입을 베이스로 하는 것으로, Syntactic Sugar의 일종이다.
프로미스를 쓰지 않아도 함수안의 코드블럭이 자동으로 프로미스로 변환된다.
async
가 앞에 붙은 함수는 항상 프로미스를 반환한다. 프로미스가 아닌 값도 resolved promise로 감싸, resolved promise가 반환되게 한다.
async
문법함수 앞에
async
를 붙이는 아주 간단한 방법이다.async function f() { return 1; } const user = f(); // async가 붙은 함수는 항상 프로미스를 반환한다. console.log(user); // Promise {<fulfilled>: 1} // 호출 방법 // 1. 변수에 할당해서 호출 user.then(console.log); // 1 // 2. 함수로 바로 호출 f().then(console.log); // 1
await
동기적인 코드를 쓰는 것처럼 만들어 promise.then
보다 가독성 좋게 프로미스의 result 값을 얻을 수 있게 해준다.
async
가 붙은 함수 안에서만 사용 가능하다.
프로미스 앞에 await
을 붙이면 자바스크립트는 프로미스가 처리(settled)될 때까지 기다린다.
💬 예제 2
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프로미스가 처리될 때까지 기다림(실행 중단)
console.log(result); // 1초뒤 완료!가 콘솔에 찍힘
}
f();
💬 예제 3
function delay(ms){
return new Promise(resolve => setTimeout(resolve, ms))
};
async function getApple(){
await delay(1000); // delay가 끝날 때까지 기다림
return '🍎'; // delay 처리 후 '🍎'을 리턴하는 프로미스가 async에 의해 만들어짐
}
getApple().then(console.log); // 🍎
// 위의 async를 promise chaining으로 표현하면?
function getApple(){
return delay(1000).then(()=>'🍎');
}
💬 예제 4 | 예제 1의 promise.then
을 async
& await
으로 바꿔보자
// 예제 1 일부
function pickFruits() {
return getApple().then(apple => {
return getBanana().then(banana => `${apple} + ${banana}`);
}); // 콜백 지옥의 향기...🔥
}
pickFruits().then(console.log); // 🍎 + 🍌
// async & await으로 변환
async function pickFruits() {
const apple = await getApple();
const banana = await getBanana();
return `${apple} + ${banana}`;
}
pickFruits().then(console.log); // 🍎 + 🍌
💬 예제 4 에서, getApple()
과 getBanana()
는 연관성이 없으므로 서로 기다려주지 않고 동시에 병렬적으로 실행되어도 된다. 이를 위해 프로미스를 선언하여 즉시 실행되게 만든다.
async function pickFruits() {
const applePromise = getApple(); // 프로미스는 선언 즉시 바로 실행됨
const bananaPromise = getBanana();
// 동기화
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
pickFruits().then(console.log); // 🍎 + 🍌
그러나 이렇게 더럽게 작성하는 것보다, Promise.all
메소드를 사용하여 깔끔하게 작성하는 것이 좋다.
Promise.all
사용하기
- 배열 내 모든 값의 이행(또는 첫 번째 거부)을 기다립니다. MDN
Promise.all([프로미스1, 프로미스2, 프로미스3]) .then(values => { console.log(values); });
function pickAllFruits() { // 프로미스 처리 결과가 담긴 배열을 기다린다 return Promise.all([getApple(), getBanana()]).then(fruits => { return fruits.join(` + `); }); // return Promise.all([getApple(), getBanana()]); } pickAllFruits().then(console.log); // 🍎 + 🍌
promise
를 async
& await
으로 변환해야 할까?그렇지 않다. 프로미스 사용에는 두 방법이 있다.
엘리님에 의하면, 프로젝트를 많이 해보며 두 방법의 차이점에 대해 감을 잡아야 한다고...
콜백 지옥도 지옥인데.. 프로미스도 지옥이고... async도 지옥이고 ..😭😭