Promise vs Async/Await 완벽 가이드
비동기 코드를 작성하다 보면 API 요청 하나는 괜찮은데 작업이 늘어나면서 .then()이 계속 이어지고, 에러 처리 위치가 헷갈리는 순간이 생긴다. Promise와 Async/Await은 서로 대체 관계가 아니라 같은 문제를 서로 다른 방식으로 해결하는 도구다. 실무에서는 대부분 두 방식을 함께 사용한다.
Promise와 Async/Await은 같은 문제를 다르게 푸는 방식이다
JavaScript는 기본적으로 단일 스레드 환경에서 동작한다. 시간이 오래 걸리는 작업을 모두 기다리면 프로그램 전체 흐름이 멈출 수 있다.
초기에는 Callback 방식이 많이 사용됐다.
getUser(function(user){
getPosts(user,function(posts){
getComments(posts,function(comments){
console.log(comments);
});
});
});
작업이 많아질수록 코드가 깊어지는 Callback Hell 문제가 발생했다.
Promise는 아직 완료되지 않은 비동기 작업의 결과를 관리하기 위한 객체다. 이후 Async/Await은 Promise를 더 읽기 쉽게 사용하도록 만든 문법이 추가됐다.
Promise의 핵심 구조와 동작 방식
Promise는 세 가지 상태를 가진다.
| 상태 | 의미 |
|---|---|
| pending | 작업 진행 중 |
| fulfilled | 작업 성공 |
| rejected | 작업 실패 |
일반적인 Promise 사용 예시는 다음과 같다.
fetch("/api/user")
.then(response=>response.json())
.then(data=>{
console.log(data);
})
.catch(error=>{
console.error(error);
})
.finally(()=>{
console.log("완료");
});
.then()은 성공 시 실행된다.
.catch()는 실패 시 실행된다.
.finally()는 결과와 관계없이 마지막에 실행된다.
Async/Await의 핵심 구조와 동작 방식
Async/Await은 Promise를 동기 코드처럼 읽기 쉽게 만든다.
async function getUser(){
const response=await fetch("/api/user");
const data=await response.json();
return data;
}
코드 구조가 위에서 아래로 읽히기 때문에 흐름 파악이 쉬워진다.
다만 await는 반드시 async 함수 내부에서 사용해야 한다.
Promise vs Async/Await 코드 가독성 비교
실제 차이는 작업이 연결될수록 커진다.
Promise 방식:
fetchUser()
.then(user=>{
return fetchPosts(user.id);
})
.then(posts=>{
return fetchComments(posts[0].id);
})
.then(comments=>{
console.log(comments);
})
.catch(error=>{
console.error(error);
});
Async/Await 방식:
async function getData(){
try{
const user=await fetchUser();
const posts=await fetchPosts(user.id);
const comments=await fetchComments(posts[0].id);
console.log(comments);
}catch(error){
console.error(error);
}
}
작업 수가 많아질수록 Async/Await 방식이 유지보수 측면에서 유리해지는 경우가 많다.
실무에서는 코드 작성보다 기존 코드 수정 시간이 더 길기 때문에 가독성이 중요한 기준이 된다.

실무에서 자주 만나는 비동기 에러 처리 패턴
비동기 코드에서 가장 자주 발생하는 문제는 예외 처리 누락이다.
다음 코드에는 문제가 있다.
fetchUser()
.then(data=>{
console.log(data);
});
실패했을 때 예외가 처리되지 않는다.
에러 처리 시 주로 사용하는 방식은 다음과 같다.
- Promise →
.catch() - Async/Await →
try/catch
Async/Await은 일반 동기 코드 예외 처리 방식과 비슷하기 때문에 흐름 추적이 비교적 쉽다.
실무에서는 언제 Promise를 쓰고 언제 Async/Await을 쓸까
실무에서는 둘 중 하나만 선택하지 않는다.
순차 작업은 Async/Await이 읽기 쉽다.
- 로그인 요청
- 사용자 정보 조회
- 권한 확인
- 추천 데이터 요청
반면 여러 API를 동시에 실행하는 경우 Promise 계열 기능이 자주 사용된다.
const [users,posts,comments]=await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
또 하나 알아둘 점이 있다.
| 방식 | 동작 |
|---|---|
| Promise.all | 하나 실패하면 전체 실패 |
| Promise.allSettled | 실패 여부와 관계없이 전체 결과 반환 |
메인 페이지 로딩처럼 사용자 정보, 게시글, 알림 데이터를 동시에 가져오는 경우에는 Promise.all 조합이 많이 사용된다.
회원 인증 이후 권한 확인처럼 순차 흐름이 필요한 경우에는 Async/Await 방식이 더 자연스럽다.
실무에서는 대부분 Async/Await으로 전체 흐름을 작성하고, 필요한 부분에서 Promise.all 또는 Promise.allSettled를 섞어 사용하는 형태가 가장 흔하다.