비동기 - callback -> Promise -> async/await 변천사

ByEunwoo
javascript

JavaScript 비동기 처리 방식의 변천사

JavaScript에서 비동기 처리 방식은 점점 더 간결하고, 직관적이며, 유지보수하기 쉬운 방향으로 발전해왔다.
이 글에서는 대표적인 3가지 비동기 처리 방식인 Callback, Promise, async/await의 변천사를 설명하고, 장단점도 비교해보겠다.


1️. Callback (콜백 함수)

초창기 JavaScript 비동기 처리 방식

작업이 끝난 후 실행할 함수를 인자로 전달해서 실행하는 방식입니다.

function fetchData(callback) {
  setTimeout(() => {
    callback('데이터 도착');
  }, 1000);
}
 
fetchData((data) => console.log(data));

장점

  • 구조가 간단하고 직관적
  • 초창기 JS에서는 이걸로 대부분 처리 가능

단점

  • 콜백 지옥 (Callback Hell)
    콜백 중첩이 깊어지면 가독성과 유지보수성 급감
  • 에러 처리 어려움
    try-catch로 예외를 잡을 수 없음
  • 디버깅 어려움
    흐름이 복잡해져 로직 추적이 어렵고, 디버깅도 힘듦
login(user, (token) => {
  getProfile(token, (profile) => {
    getPosts(profile.id, (posts) => {
      // 콜백 지옥 시작...
    });
  });
});

2. Promise (ES6, 2015)

콜백의 한계를 극복하기 위해 등장

비동기 작업의 결과를 Promise 객체로 감싸고,
.then(), .catch() 체이닝으로 처리합니다.

fetchData()
  .then((data) => processData(data))
  .then((result) => console.log(result))
  .catch((err) => console.error(err));

장점

  • 체이닝으로 가독성 향상
  • catch로 명확한 에러 처리
  • Promise.all, Promise.race 등으로 동시 비동기 처리 제어 가능

단점

  • .then() 체인이 길어지면 여전히 복잡함
  • 동기 코드처럼 읽히지 않아서 흐름 파악이 어려움

💡 Promise 상태

  • Pending: 대기 중
  • Fulfilled: 성공
  • Rejected: 실패

3. async/await (ES2017)

Promise를 더 간결하게 쓰기 위한 문법

async 함수 내에서 await 키워드를 사용하면,
비동기 작업을 마치 동기처럼 처리할 수 있다.

async function handleData() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    console.log(result);
  } catch (err) {
    console.error(err);
  }
}

장단

  • 동기 코드처럼 보여 가독성 탁월
  • try-catch로 에러 처리 용이
  • 흐름 추적이 쉬워서 디버깅 편리

단점

  • await는 기본적으로 순차 실행이라 병렬 처리 어려움
  • 내부적으로는 여전히 Promise 객체 기반 → JS 엔진은 Generator 기반으로 처리

병렬 처리 해결 방법

async function loadAll() {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
  console.log(a, b);
}

Generator란?

Generator 함수는 일반 함수와 다르게 실행을 일시 중단하고,
필요할 때 재개(resume) 할 수 있는 함수이다.

function* gen() {
  yield 1;
  yield 2;
  return 3;
}
 
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }

async/await는 내부적으로 이 Generator와 유사한 방식으로 작동합니다.

세 가지 방식 비교

구분CallbackPromiseasync/await
등장 시기초창기 JavaScriptES6 (2015)ES2017
문법 구조함수 인자로 콜백 전달.then().catch() 체이닝async, await + try-catch
가독성❌ 중첩 시 매우 나쁨✅ 중간 정도✅ 매우 좋음
에러 처리❌ 어려움 (try-catch 불가)catch 가능try-catch 가능
병렬 처리❌ 어려움Promise.all 등 사용 가능❌ 순차 실행, ✅ Promise.all로 보완
흐름 추적❌ 어려움then 체인 많으면 어려움✅ 동기처럼 읽힘

정리

  • 콜백 → Promise → async/await로 갈수록 코드가 깔끔해지고 가독성이 높아졌음
  • 하지만 **기본 개념(비동기 처리 방식)**은 모두 같고, 표현 방식이 다를 뿐!
  • 실제 프로젝트에서는 세 가지 방식이 혼용되기도 하며, 상황에 맞게 선택하는 유연함이 필요하다.
Posted injavascript
Written byEunwoo