Post

JAVASCRIPT 콜백 지옥

콜백 지옥이란?

▶ 콜백 지옥(Callback Hell)은 JavaScript 코드에서 콜백 함수를 중첩해서 사용할 때 발생하는 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만드는 상황을 의미합니다. 콜백 지옥의 경우 비동기적인 작업을 처리할 때 많이 발생하는데 콜백 함수를 연속적으로 사용할 때 코드가 깊게 중첩되어 복잡해지기 때문입니다.
아래는 간단한 콜백 지옥의 예시입니다.

1
2
3
4
5
6
7
getUser(userId, function(user) {
  getOrders(user, function(orders) {
    getOrderDetails(orders[0], function(orderDetails) {
      // 원하는 작업 수행
    });
  });
});

▶ 위 코드에서는 getUser, getOrders, getOrderDetails와 같은 비동기 함수들이 콜백 형태로 사용되고 있습니다.
이런 형태로 콜백이 계속 중첩되면 코드가 아래로 계속 들어가면서 가독성이 떨어지고, 오류를 찾기 어려워집니다.

콜백 지옥을 해결하는 방법

▶ 그렇다면 해결방법이 없을까? 아닙니다! 콜백 지옥을 해결하는 방법 아래에서 알려드릴게요.

1. 콜백 대신 프로미스(Promise) 사용
  • 프로미스는 비동기 작업을 좀 더 구조적이고 순차적으로 처리할 수 있게 해주는 객체로 프로미스를 사용하여 콜백 지옥을 피할 수 있습니다. 아래의 예시를 통해 살펴볼게요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function getUser(userId) {
  return new Promise((resolve, reject) => {
    // 비동기 작업
    setTimeout(() => {
      const user = { id: userId, name: 'Alice' };
      resolve(user);
    }, 1000);
  });
}

function getOrders(user) {
  return new Promise((resolve, reject) => {
    // 비동기 작업
    setTimeout(() => {
      const orders = [{ id: 1, product: 'Product A' }, { id: 2, product: 'Product B' }];
      resolve(orders);
    }, 1000);
  });
}

function getOrderDetails(order) {
  return new Promise((resolve, reject) => {
    // 비동기 작업
    setTimeout(() => {
      const orderDetails = { id: order.id, total: 100 };
      resolve(orderDetails);
    }, 1000);
  });
}

getUser(123)
  .then(user => getOrders(user))
  .then(orders => getOrderDetails(orders[0]))
  .then(orderDetails => {
    console.log(orderDetails); // { id: 1, total: 100 }
    // 원하는 작업 수행
  })
  .catch(error => {
    console.error(error); // 오류 처리
  });

▶ 위의 예시는 프로미스를 사용하여 getUser, getOrders, getOrderDetails 함수들은 각각 프로미스를 반환하고, 이를 then 메서드로 연결하여 비동기 작업을 순차적으로 처리했습니다. 그리고 마지막에는 catch 메서드로 오류를 처리했습니다.
💡 추가 보충 설명

▶ return new Promise((resolve, reject) => { … })은 JavaScript에서 프로미스 객체를 생성하는 방법 중 하나입니다.
이 구문은 비동기 작업을 수행하고 그 결과를 처리할 수 있는 프로미스를 생성하는데 여기서 resolve와 reject는 프로미스의 상태를 변경하는 데 사용되는 콜백 함수입니다.

  • resolve: 프로미스를 성공 상태로 변경하고, 비동기 작업의 결과 값을 전달합니다.

  • reject: 프로미스를 실패 상태로 변경하고, 오류 정보를 전달합니다.


2. Async/Await 사용
  • Async 함수와 Await 키워드를 사용하면 비동기 코드를 동기적으로 작성할 수 있어 이를 통해 콜백 지옥을 피할 수 있습니다. 아래의 예시를 통해 살펴볼게요.
1
2
3
4
5
6
7
8
9
10
11
12
async function processOrders(userId) {
  try {
    const user = await getUser(userId);
    const orders = await getOrders(user);
    const orderDetails = await getOrderDetails(orders[0]);
    // 원하는 작업 수행
  } catch (error) {
    // 오류 처리
  }
}

processOrders(userId);


2-1. Async함수
  1. async 함수는 함수 앞에 async 키워드를 붙여서 정의합니다. 이렇게 정의된 함수는 항상 프로미스를 반환하게 됩니다.
  2. async 함수 내부에서 await 키워드를 사용하여 비동기 작업의 완료를 기다릴 수 있습니다. 이때 await는 프로미스가 처리될 때까지 함수의 실행을 일시 중지시키고, 해당 프로미스가 처리되면 그 결과를 반환합니다.
  3. async 함수를 사용하면 비동기 코드를 동기식으로 작성할 수 있어 가독성이 높아지고, 콜백 지옥을 피할 수 있습니다.


2-2. Await키워드
  1. await 키워드는 async 함수 내에서만 사용할 수 있습니다. 비동기 작업의 완료를 기다리고 그 결과를 반환하는 역할을 합니다.
  2. await 키워드를 사용할 때에는 프로미스 앞에 붙여서 사용하며, 프로미스가 처리될 때까지 함수의 실행을 일시 중지시킵니다.
  3. await 키워드를 사용할 때 주의할 점은, await 키워드는 반드시 async 함수 내에서만 사용할 수 있으며, 최상위 레벨에서는 사용할 수 없습니다.


이렇게 오늘은 콜백 지옥에 대해 알아봤는데요,
콜백 지옥은 코드의 가독성과 유지보수성을 떨어뜨리므로, 프로미스나 Async/Await를 사용하여 비동기 코드를 더 간결하고 구조적으로 작성하는 것이 좋다는 거 잊지마세요!
오늘 정리한 내용도 면접을 준비할 때 나올 수 있는 질문인만큼 미리미리 숙지해서 알아두면 좋겠죠?
그럼 다음엔 또 다른 면접 정보들을 가져와보도록 하겠습니다 😁!

This post is licensed under CC BY 4.0 by the author.