then
으로 엮여 지저분해진 promise chaining을 좀 더 깔끔하게 정리하는 방법으로, 비동기적인 함수를 async
키워드로 선언하고 내부적으로 promise
의 resolve된 값을 await
로 받아와 동기적으로 작성하는 것을 가능하게 한다.
async/await
가Promise
를 대체하는 문법이라고 생각하면 안된다.Promise
를 사용해야 하는 경우도 존재하기 때문이다.
async
function 앞에 async
키워드를 붙이면 해당 함수는 항상 Promise 객체를 반환하게 되며, Promise가 아닌 값을 반환하더라도 이행 상태의 Promise(resolved promise) 객체로 감싸 이행된 Promise가 반환되도록 한다.
async function f() {
return Promise.resolve('이행될 Promise');
}
f().then(alert); // '이행될 Promise'
await
async
함수 내에서만 동작하며, await
를 포함하는 문장은 해당 async
문의 promise가 이행될 때까지 기다렸다가(중단) promise가 이행되면 실행을 재개한다. 따라서, 이런 중단이 발생하면 안되는 로직에서는 Promise.all()
적용을 고려할 수 있다.
promise.then
보다 좀 더 깔끔하게 result
값을 얻을 수 있도록 하여 보다 가독성이 높고 사용성도 편리하다.// 최상위 레벨 코드에선 문법 에러가 발생함
let response = await fetch('url');
let user = await response.json();
최상위 레벨 코드에서 await
를 사용하려면 async
함수로 코드를 감싼 IIFE(즉시 실행 함수) 형태로 작성해야 한다.
(async () => {
let response = await fetch('url');
let user = await response.json();
...
})();
이러한 문법 제약으로 최종 결과나 처리되지 못한 에러를 최상위 레벨 코드에서 다루어야 할 때는 then/catch
를 추가해야 한다.
async function f() {
let response = await fetch('잘못된 url');
}
// f()는 거부 상태의 Promise
f().catch(alert);
async가 아닌 함수에서 async 함수 호출하기
async
함수를 호출하면 프라미스가 반환되므로,.then
을 붙이면 간단히 해결할 수 있다.async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; } function f() { // shows 10 after 1 second wait().then(result => alert(result)); } f();
프라미스가 거부되면 마치 throw
문을 작성한 것처럼 에러가 던져진다.
async function f() {
await Promise.reject(new Error("에러 발생!"));
}
// 위 코드와 동일
async function f() {
throw new Error("에러 발생!");
}
이렇게 던져진 에러는 throw
가 던진 에러를 잡을 때처럼 try .. catch
를 사용해 잡을 수 있다.
async function f() {
try {
let response = await fetch('http://유효하지-않은-주소');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
await
+ Promise.all
만약, 각 Promise가 서로 관련이 없는 작업이라면 각 비동기 작업의 종료를 기다려야 하므로 비효율적인 동작이 수행될 수 있다. 예를 들어, a, b 작업이 각각 1s, 2s 가 걸리는 비동기 작업일 때, await
로 동기적으로 작성하면 총 3s를 기다려야 한다.
여러 개의 Promise가 병렬적으로 모두 처리되길 기다리는 상황에서는 Promise.all
과 함께 사용할 수 있다. async/await
는 ‘순차적’ 개념인 반면, Promise.all
은 ‘동시’ 개념이라고 이해하면 쉽다.
// Promise 처리 결과가 담긴 배열을 기다림
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
await
와 Promise.all
을 조합하여 여러 개의 Promise 처리하기프로젝트 중, ‘최근 일주일 운전자 점수를 조회’하는 기능을 구현하게 되었다. 이때, 백엔드 팀원이 일주일 정보를 불러오는 api를 따로 만들지 않은 상태였다. 팀원이 만들기 귀찮다고 하는데 어쩌겠냐.. 반복문 돌려야지..!
우선, 요일별 점수 데이터가 모두 성공 상태여야 하나?를 고려했다. 일주일 운전자 점수 조회를 했는데 특정 요일 점수는 비어있는데, 해당 점수만 비우고 보여주는 것도 다소 이상하다고 생각했다. 따라서 ‘모 아니면 도’ 방식의 Promise.all()
방식을 채택했고, useQuery
를 포함하는 훅을 작성했다. 이때, 페칭 작업(여기에서는 axios 사용)이 비동기 작업이므로 각각의 resolve된 Promise는 await로 반환되어야 한다. 전체 코드는 아래와 같다.
// GET: 최근 일주일 운전자 점수 조회
export const useGetRecentSevenDaysDriverActions = () => {
return useQuery<number[]>({
queryKey: ["recent-seven-days-score"],
queryFn: async () => {
const recentSevenDaysDriverActionsURL = `/api/actions/scores/sum`;
const recentSevenDaysScore = [];
// 최근 7일의 각 날짜의 합산을 구하기 위한 요청 생성
for (let i = 6; 0 <= i; i--) {
const score = axiosInstance
.get(recentSevenDaysDriverActionsURL, {
params: { date_start, date_end },
})
.then((res) => res.data._sum.score);
recentSevenDaysScore.push(score);
}
// 모든 날짜의 요청이 완료될 때까지 기다리고, 결과를 배열로 반환
return await Promise.all(recentSevenDaysScore);
},
});
};
참고자료
async function - JavaScript | MDN
Graceful asynchronous programming with Promises - Web 개발 학습하기 | MDN
async와 await
await - JavaScript | MDN
자바스크립트 13. 비동기의 꽃 JavaScript async 와 await 그리고 유용한 Promise APIs | 프론트엔드 개발자 입문편 (JavaScript ES6)
자바스크립트 비동기 프로그래밍 #4 | Async & Await