Callback Hell

콜백 지옥은 여러 비동기 작업을 순차적으로 처리해야 할 때 발생하는 중첩된 콜백 구조를 말한다. 코드가 깊게 중첩되어 가독성이 떨어지고 유지보수가 어려워지는 문제가 있다.

이는 주로 여러 비동기 작업(API 호출, 파일 읽기, 타이머 등)이 순차적으로 실행되어야 할 때 발생하며, 코드가 피라미드 형태로 깊이 중첩되는 특징을 보인다.

콜백 지옥의 원인

  • 비동기 작업의 연속성:
    A 작업의 결과가 B 작업의 입력으로 필요할 때, 콜백 안에 콜백을 중첩해야 한다.
  • JavaScript의 단일 스레드 특성:
    비동기 작업 완료 시점을 콜백 함수로 처리해야 하므로 중첩이 불가피한다.

주요 문제점

문제점설명
가독성 저하코드의 들여쓰기 수준이 과도하게 증가하여 논리 흐름 파악이 어렵다.
에러 처리 복잡각 콜백마다 별도의 에러 핸들링이 필요해 중복 코드가 발생한다.
유지보수 난이도기능 수정 시 중첩 구조에서 의도치 않은 버그 발생 가능성이 높다.

해결 방법

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getYetEvenMoreData(c, function(d) {
        getFinalData(d, function(finalData) {
          console.log("최종 데이터:", finalData);
        }, errorCallback);
      }, errorCallback);
    }, errorCallback);
  }, errorCallback);
}, errorCallback);

함수 분리 (모듈화)

각 콜백을 별도의 명명된 함수로 분리하여 중첩을 줄인다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function handleFinalData(finalData) {
  console.log("최종 데이터:", finalData);
}

function handleD(d) {
  getFinalData(d, handleFinalData, errorCallback);
}

function handleC(c) {
  getYetEvenMoreData(c, handleD, errorCallback);
}

function handleB(b) {
  getEvenMoreData(b, handleC, errorCallback);
}

function handleA(a) {
  getMoreData(a, handleB, errorCallback);
}

getData(handleA, errorCallback);

Promise 사용

Promise는 비동기 작업의 결과를 나타내는 객체로, 콜백 지옥을 해결하기 위한 더 나은 방법을 제공한다.
.then()을 사용해 비동기 작업을 순차적으로 연결하고, .catch()로 통합 에러 처리 가능하다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
getDataPromise()
  .then(a => getMoreDataPromise(a))
  .then(b => getEvenMoreDataPromise(b))
  .then(c => getYetEvenMoreDataPromise(c))
  .then(d => getFinalDataPromise(d))
  .then(finalData => {
    console.log("최종 데이터:", finalData);
  })
  .catch(error => {
    console.error("에러 발생:", error);
  });

Async/Await 사용

ES2017에서 도입된 Async/Await는 비동기 코드를 동기 코드처럼 작성할 수 있게 해준다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
async function processData() {
  try {
    const a = await getDataPromise();
    const b = await getMoreDataPromise(a);
    const c = await getEvenMoreDataPromise(b);
    const d = await getYetEvenMoreDataPromise(c);
    const finalData = await getFinalDataPromise(d);
    console.log("최종 데이터:", finalData);
  } catch (error) {
    console.error("에러 발생:", error);
  }
}

processData();

콜백 지옥 vs. 해결 후 코드 비교

콜백 지옥 코드Promise/async-await 코드
깊은 들여쓰기와 계층적 구조평평한 구조와 직관적인 흐름
분산된 에러 처리단일 .catch() 또는 try/catch로 통합

추가 전략

  • 병렬 처리: Promise.all()로 독립적인 비동기 작업을 동시 실행한다.

    1
    2
    3
    
    Promise.all([getUser(), getPosts(), getComments()])
      .then(results => console.log(results))
      .catch(error => console.error(error));
    
  • 라이브러리 활용: async.jsRxJS로 복잡한 비동기 흐름을 관리한다.


참고 및 출처