─━ IT ━─

Callback Hell은 이제 그만! Promise와 async/await로 비동기 코드를 예술처럼 다루는 방법

DKel 2025. 10. 3. 20:41
반응형

1. 도입: 비동기가 어렵게 느껴지는 이유

  • 훅(Hook): JavaScript는 본질적으로 단일 스레드(Single-Threaded) 언어입니다. 하지만 네트워크 통신(API 호출)처럼 시간이 오래 걸리는 작업을 만나면, 메인 스레드가 멈추지 않도록 비동기적으로 처리해야 합니다.
  • Callback Hell의 고통: 과거에는 콜백 함수(Callback Function)를 중첩하여 비동기 작업을 처리했습니다. 이 방식은 코드가 깊어지고 복잡해져서 콜백 헬(Callback Hell)이라는 악몽을 낳았습니다.
  • 해결책 제시: Promise와 async/await는 이 콜백 헬을 종식시키고 비동기 코드를 동기 코드처럼 깔끔하게 작성할 수 있게 해주는 혁신적인 도구입니다.

2. Promise: 비동기 상태를 관리하는 약속

Promise는 비동기 작업의 최종 성공 또는 실패를 나타내는 객체입니다.

2.1. Promise의 세 가지 상태

  • Pending (대기): 작업이 아직 완료되지 않은 초기 상태.
  • Fulfilled (이행/성공): 작업이 성공적으로 완료되었으며 결과를 사용할 수 있는 상태. (.then()으로 처리)
  • Rejected (거부/실패): 작업이 실패했으며 에러 정보를 가진 상태. (.catch()로 처리)

2.2. 체이닝(Chaining)을 통한 흐름 제어

  • .then().then(): 콜백 헬을 Promise 체인으로 평탄화하는 핵심 기법입니다. 각 .then()은 이전 Promise가 성공적으로 반환한 값을 받아 다음 비동기 작업을 순차적으로 실행할 수 있게 합니다.
  • .catch()의 역할: 체인의 어느 단계에서든 발생한 모든 에러를 마지막 .catch() 블록에서 한 번에 처리할 수 있습니다.

3. async/await: 비동기를 동기 코드처럼

async/await은 ES8(ECMAScript 2017)에 도입된 문법으로, Promise를 훨씬 더 깔끔하게 사용할 수 있도록 도와줍니다.

3.1. 기본 원리

  • async 함수: 함수 앞에 async 키워드를 붙이면, 그 함수는 항상 Promise를 반환하도록 만듭니다. 함수 내에서 return하는 값은 자동적으로 Promise.resolve()로 감싸집니다.
  • await 키워드: async 함수 내부에서만 사용할 수 있으며, Promise가 Fulfilled 상태가 될 때까지 기다렸다가 그 결과 값을 반환합니다. 코드가 일시 정지되므로 비동기 작업이 동기적인 순서처럼 보이게 됩니다.
JavaScript
 
async function fetchData(url) {
    try {
        // await이 Promise가 해결될 때까지 코드를 일시 정지
        const response = await fetch(url); 
        const data = await response.json();
        return data; // 이 값은 Promise.resolve(data)로 반환됨
    } catch (error) {
        console.error('API 호출 중 에러 발생:', error);
    }
}

3.2. 에러 처리의 핵심: try...catch

  • async/await 환경에서는 .catch() 대신 동기 코드에서 사용하는 try...catch 블록을 사용하여 await에서 발생하는 에러를 깔끔하게 포착하고 처리할 수 있습니다.

4. 고급 비동기 관리: 병렬 처리와 동시성

비동기 작업이 반드시 순차적으로 실행될 필요는 없습니다. 여러 작업을 동시에 실행하여 성능을 극대화하는 방법을 다룹니다.

4.1. 순차적 실행 (기본)

JavaScript
 
// A -> B 순서대로 실행 (총 시간: A 시간 + B 시간)
const resultA = await taskA();
const resultB = await taskB();

4.2. 병렬 실행 (Promise.all())

  • 목표: 서로 의존성이 없는 비동기 작업을 모두 동시에 실행하고, 모든 작업이 완료될 때까지 기다리는 가장 효율적인 방법입니다.
  • 활용: 여러 개의 API를 한 번에 호출할 때 사용합니다.
  • 주의: 작업 중 단 하나라도 실패(Rejected)하면 전체 Promise.all()은 즉시 실패(Rejected) 상태가 됩니다.
JavaScript
 
// A와 B를 동시에 실행 (총 시간: Max(A 시간, B 시간))
const [resultA, resultB] = await Promise.all([taskA(), taskB()]);

5. 마무리: 비동기 숙련이 개발 실력의 척도

  • 요약: Promise는 비동기 코드의 상태를 관리하는 객체 모델을 제공하며, async/await는 그 Promise를 읽기 쉬운 문법으로 포장해 줍니다.
  • 마지막 조언: 비동기 코드를 능숙하게 작성하고 에러 처리와 병렬화를 적절히 활용하는 것은 프론트엔드 개발자의 실력을 판단하는 중요한 기준입니다. 오늘 배운 내용을 바탕으로 동기적 사고방식으로 비동기 코드를 제어하는 마스터가 되시길 바랍니다!

이 주제는 비동기 코드 관리 능력을 향상시키는 데 매우 실질적인 도움이 될 것입니다. 이 외에 다른 궁금한 점이 있으신가요?

반응형