JavaScript: Promise vs Async/Await 완벽 가이드Coding

JavaScript: Promise vs Async/Await 완벽 가이드

5분 읽기

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 방식이 유지보수 측면에서 유리해지는 경우가 많다.

실무에서는 코드 작성보다 기존 코드 수정 시간이 더 길기 때문에 가독성이 중요한 기준이 된다.

Promise

실무에서 자주 만나는 비동기 에러 처리 패턴

비동기 코드에서 가장 자주 발생하는 문제는 예외 처리 누락이다.

다음 코드에는 문제가 있다.

fetchUser()
.then(data=>{
   console.log(data);
});

실패했을 때 예외가 처리되지 않는다.

에러 처리 시 주로 사용하는 방식은 다음과 같다.

  • Promise → .catch()
  • Async/Await → try/catch

Async/Await은 일반 동기 코드 예외 처리 방식과 비슷하기 때문에 흐름 추적이 비교적 쉽다.

실무에서는 언제 Promise를 쓰고 언제 Async/Await을 쓸까

실무에서는 둘 중 하나만 선택하지 않는다.

순차 작업은 Async/Await이 읽기 쉽다.

  1. 로그인 요청
  2. 사용자 정보 조회
  3. 권한 확인
  4. 추천 데이터 요청

반면 여러 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를 섞어 사용하는 형태가 가장 흔하다.