Frontend/Javascript Essentials

비동기 처리: CallBack → Promise → async/await

JTB 2025. 10. 13. 23:20

자바스크립트에서 비동기 처리를 처음 접하면 대부분 콜백 함수를 사용한다.

하지만 전통적인 콜백 패턴에는 몇 가지 문제점이 있다.

 

1. 콜백 패턴의 문제점

1. 콜백 헬 (Callback Hell)

비동기 함수 내부에서 또 다른 비동기 작업을 수행해야 할 때, 콜백 함수가 중첩되며 코드 복잡도가 급격히 증가한다.

getData((res1) => {
  getMoreData(res1, (res2) => {
    getExtraData(res2, (res3) => {
      console.log(res3);
    });
  });
});

코드가 계속 오른쪽으로 들여쓰기되어 가독성이 나쁘다.

또한 비동기 처리 결과를 함수 외부로 반환하거나 상위 스코프 변수에 할당하기 어렵다.

2. 에러 처리의 어려움

콜백 내부에서 발생한 에러는 외부 try/catch로 잡기 어렵다.

예를 들어, setTimeout의 콜백에서 발생한 에러는 이미 호출한 함수가 콜 스택에서 제거되었기 때문에 잡히지 않는다.

 

각 콜백마다 개별적으로 에러 핸들링을 해야 하고, 비동기 단계가 많아질수록 에러 전파가 어렵다.

이런 이유로 콜백 패턴만 사용하면 코드가 복잡하고 유지보수가 어렵다.

 

2. Promise: 콜백 패턴의 대안

ES6에서 도입된 Promise는 비동기 작업의 상태를 명확히 표현하고, 콜백 패턴의 단점을 보완한다.

const promise = new Promise((resolve, reject) => {
  // 비동기 처리 수행
  if (성공) resolve(result);
  else reject(error);
});
  • resolve: 성공 시 호출, 결과 전달
  • reject: 실패 시 호출, 에러 전달
  • 프로미스 상태는 pending → fulfilled / rejected로 결정된다.

즉, Promise는 비동기 처리 상태와 결과를 관리하는 객체다.

1. 후속 처리 메서드

.then()

  • 첫 번째 콜백: Promise가 성공(resolve)했을 때 호출된다.
  • 두 번째 콜백: Promise가 실패(reject)했을 때 호출된다.
promise.then(
  (result) => console.log(result),
  (error) => console.error(error)
);

.catch()

  • 실패(rejected) 상태일 때만 호출된다.
  • 체이닝 중간의 에러까지 모두 잡을 수 있다.
promise
  .then((result) => doSomething(result))
  .catch((error) => console.error(error));

 

.finally()

  • 성공/실패 관계없이 항상 호출된다.
promise.finally(() => console.log("작업 완료"));

 

2. 프로미스 체이닝 (Promise Chaining)

.then().catch()항상 새로운 Promise를 반환하므로 연속 호출이 가능하다.

fetch('/data.json')
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((err) => console.error(err))
  .finally(() => console.log("작업 완료"));

여러 비동기 작업을 순차적으로 처리할 수 있다. 하지만 체인이 길어지면 여전히 가독성이 떨어진다.

 

3. async/await: 프로미스 문법적 설탕

async/awaitPromise 위에서 동작하는 문법적 설탕이다. 동기 코드처럼 읽기 쉽고 try/catch로 에러 처리가 간단하다.

async function getData() {
  try {
    const res = await fetch('/data.json');
    const data = await res.json();
    console.log(data);
  } catch (e) {
    console.error(e);
  } finally {
    console.log("작업 완료");
  }
}

getData();
  • async : 함수가 항상 Promise를 반환하게 한다.
  • await : Promise가 처리될 때까지 기다리고 결과를 변수에 할당한다.

즉, 비동기 코드도 마치 동기 코드처럼 작성할 수 있다.

 

3. 요약

방식 장점 단점
콜백 간단한 단일 비동기 중첩 시 콜백 헬, 에러 처리 어려움
Promise 상태 관리 가능, 체이닝 가능 체인 길어지면 가독성 떨어짐
async/await 동기 코드처럼 읽기 쉬움, try/catch로 에러 처리 가능 병렬 처리 시 주의 필요

 

  • 단순 비동기 → 콜백도 가능
  • 연속/복잡한 비동기 → Promise 또는 async/await 추천
  • 실무 → 대부분 async/await + try/catch 조합을 사용한다.