Promise, async and await
2022-05-20
자바스크립트에서 비동기 함수를 순차적으로 호출하는 방법 중에는 콜백, Promise, async 함수를 이용한 방법이 있습니다.
콜백을 이용하는 방법
function pickApple(callback) {
setTimeout(() => {
console.log("picking an apple...");
callback("apple");
}, 1000);
}
function eat(food, callback) {
setTimeout(() => {
console.log(`eating ${food}...`);
callback(food);
}, 3000);
}
function goToSleep(food) {
setTimeout(() => {
console.log(`ate ${food}...going to bed`);
}, 3000);
}
pickApple((food) => {
eat(food, (food) => goToSleep(food));
});
콜백으로 비동기 로직을 처리하기 위해서는 비동기 로직 후에 그 다음 함수가 실행되도록 전의 비동기 함수에 callback 파라미터를 받아 함수 안에서 비동기 로직이 모두 실행된 다음 callback으로 다음 함수를 실행할 수 있도록 해야합니다.
다음과 같은 문제가 있었습니다.
- 비동기 함수 다음 처리해야 하는 로직이 있을 때마다 콜백을 argument로 넘겨줘야 한다.
- 에러 처리를 할 경우 에러 핸들링 함수도 콜백으로 넘겨주어야 한다.
- 순서대로 진행되어야 하는 함수가 많을수록 더 깊은 콜백이 필요하다.
Promise를 쓰는 이유
- Promise의 resolve를 통해 다음 순서에 필요한 콜백을 parameter로 넘겨줄 필요 없이 호출된 코드 라인으로 값을 넘겨줄 수 있다.
- resolve를 통해 전달된 결과 값을 then으로 받아 처리할 수 있다.
- reject를 통해 에러를 전달받아 핸들링 할 수 있다.
Promise를 이용하는 방법
function pickApple() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("picking an apple...");
resolve("apple");
}, 1000);
});
}
function eat(food) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`eating ${food}...`);
resolve(food);
}, 3000);
});
}
function goToSleep(food) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`ate ${food}...going to bed`);
}, 3000);
});
}
pickApple().then((food) => {
eat(food).then(() => {
pickApple().then((food) => {
eat(food).then((food) => {
goToSleep(food);
});
});
});
});
콜백 지옥과 Promise.then 중첩의 차이점
- 콜백과 달리 promise로 순서가 있는 비동기 함수를 실행할 때 promise의 리턴값이 then으로 넘겨지기 때문에 then이 계속해서 중첩되게 된다.
async await 키워드를 이용하는 방법
function hello () {
return Promise.resolve(‘hello’);
}
async function hello () {
return ‘hello’;
}
위의 두 코드는 같습니다. 두 번째 코드는 첫 번째 코드의 syntatic sugar라고 할 수 있습니다.
function delay() {
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
async function pickApple() {
await delay();
return "apple";
}
async function pickBanana() {
await delay();
return "banana";
}
async function asyncPickAllFruits() {
const applePromise = pickApple();
const bananaPromise = pickBanana();
const apple = await applePromise;
const banana = await bananaPromise;
console.log([apple, banana].join(" + "));
}
function pickAllFruits() {
Promise.all([pickApple(), pickBanana()]).then((fruits) => {
console.log(fruits.join(" + "));
});
}
pickAllFruits();
await await연산자는 Promise를 기다리기 위해 사용됩니다. 연산자는 async function 내부에서만 사용할 수 있습니다.
await vs return vs return await
다음 예제를 통해 async await이 어떻게 동작하게 되는지 예측해보며 개념에 대한 이해를 더 정확하게 체크할 수 있었습니다. 해당 예제는 여기에서 가져왔습니다.
async function waitAndMaybeReject() {
// Wait one second
await new Promise((r) => setTimeout(r, 1000));
// Toss a coin
const isHeads = Boolean(Math.round(Math.random()));
if (isHeads) return "yay";
throw Error("Boo!");
}
just calling
async function foo() {
try {
waitAndMaybeReject();
} catch (e) {
return "caught";
}
}
waitAndMaybeReject는 async 함수이기 때문에 무조건 Promise를 반환합니다.
일반 함수였다면 50퍼센트의 확률로 'yay'라는 문자열을 반환했겠지만,
async 함수이기 때문에 50퍼센트의 확률로 'yay'를 resolve하는 Promise 혹은 Error('Boo!')로 reject하는 Promise 가 반환됩니다.
두 값 모두 resolve, reject가 되지 않은 pending 상태의 Promise이기 때문에 위의 코드에서 절대 에러가 발생하지 않게 됩니다.
awaiting
async function foo() {
try {
await waitAndMaybeReject();
} catch (e) {
return "caught";
}
}
await 키워드를 사용하므로써, pending상태의 Promise가 resolve 또는 reject 처리가 됩니다.
waitAndMaybeReject에서 isHeads가 true이면 foo에서 resolve처리가 되고, isHeads가 false이면 foo에서 error가 발생하게 됩니다.
returning
async function foo() {
try {
return waitAndMaybeReject();
} catch (e) {
return "caught";
}
}
await 키워드 없이 waitAndMaybeReject를 바로 return할 경우에도 첫번째 just calling의 경우와 같이 pending 상태의 Promise이기 때문에 catch 블록이 절대 실행되지 않습니다.
return-awaiting
async function foo() {
try {
return await waitAndMaybeReject();
} catch (e) {
return "caught";
}
}
그렇다면 Promise를 받아 await 처리를 하고, 이를 return하는 async 함수는 어떻게 동작할까요?
isHeads = true로 나왔다면
- waitAndMaybeReject로부터 pending 상태의 Promise를 받는다.
- await를 통해 resolve 처리한다.
- resolve 된 경우 이 값을 다시 Promise.relove('yay')로 반환한다.
isHeads = false로 나왔다면
- waitAndMaybeReject로부터 pending 상태의 Promise를 받는다.
- await를 통해 reject 처리한다.
- Error('Boo!')로 인해 catch 블록이 실행된다.
try-catch 문이 없다면 Promise의 resolve, reject 처리가 된 결과값을 다시 Promise로 반환하는 것은 redundant한 일입니다.
그렇기 때문에 try-catch 블록을 사용하지 않는 경우에 한에 이를 제한하는 eslint 규칙이 존재합니다.
참고자료
Guide on using promises
Promise apis
await vs return vs return await