JavaScript 비동기 처리: Promise와 async/await 비교
배경
레거시 코드베이스에 콜백이 4~5단계로 중첩된 부분이 많았다. ES6로 마이그레이션하면서 Promise로 전환했고, 최근 Node 8에서 async/await가 정식 지원되면서 일부를 다시 리팩토링했다.
Promise 방식
function getUserData(userId) {
return fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => {
return { comments };
})
.catch(error => {
console.error('Error:', error);
throw error;
});
}
Promise 체이닝은 콜백보다 훨씬 낫지만, 중간에 변수를 참조해야 할 때 클로저나 Promise.all을 써야 해서 복잡했다.
async/await 방식
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.error('Error:', error);
throw error;
}
}
동기 코드처럼 읽히고, 중간 변수 접근이 자유롭다. 에러 핸들링도 try/catch로 통일할 수 있어서 직관적이었다.
병렬 처리 주의점
// 순차 실행 (느림)
const user = await fetchUser(id);
const config = await fetchConfig(id);
// 병렬 실행 (빠름)
const [user, config] = await Promise.all([
fetchUser(id),
fetchConfig(id)
]);
await를 연속으로 쓰면 순차 실행된다. 독립적인 요청은 Promise.all로 묶어야 성능 이슈가 없다.
결론
새로운 비동기 코드는 async/await로 작성하고 있다. Promise는 여전히 유용하지만, 가독성 면에서 async/await가 명확히 우위였다. 다만 브라우저 지원을 위해 Babel 트랜스파일은 필수다.