본문으로 바로가기

[JavaScript] callback과 callback 지옥

category 공유/JavaScript, TypeScript 2022. 2. 17. 06:02

[JavaScript] callback과 callback 지옥

개발하다 보면 한번쯤은 callback 지옥을 경험하게 됩니다. 이때 불편함을 느끼고 많은 개발자들이 promise, async, await을 학습하여 callback 지옥을 벗어납니다

지옥을 벗어나기 전 이번 게시글에서는 callback 함수가 무엇인지? callback 지옥이 무엇인지? 한번 알아보도록 하겠습니다.

callback

callback, callback이라고 하는데 callback 함수가 무엇일까? callback 함수란 특정 동작을 하고 난 뒤에 추가로 동작할 함수이다. 어떤 동작이 작동한 후에 이것을 실행해줘하고 등록된 함수라고 하는 것이 더 이해하기 쉬울 것 같다. 예를들어 밥 먹은 뒤 설거지 해 라고 한다면 설거지 하는 것이 callback 함수라고 생각하면 됩니다. (더 헷갈리나 ...?) callback 함수는 동기적 callback 함수비동기적 callback 함수가 존재합니다. 각각에 대해 조금 더 자세히 알아보도록 하겠습니다.

◆ 동기적 callback 함수

호출을 하였으면 호출에 대한 응답을 기다리고 응답을 받은 후 다음 작업을 하는 것입니다. 아래 코드를 확인해봅시다.

// 동기 callback / sync callback
const syncDishWashing = dishWashing => dishWashing();

console.log("아들아 설거지 해라");
// 엄마가 아들 설거지 하는 걸 지켜보는 중 ...
syncDishWashing(() => console.log("설거지 다 했어"));
// 엄마가 아들 설거지 하는 걸 지켜보는 중 ...
console.log("잘했어. 엄마가 빨래하는 거 보고 배워");
동기적 callback

실생활에서 나올 수 있는 예시로 작성하였는데 이해가 되시나요?

1. 엄마가 아들에게 설거지 명령

2. 엄마가 아들이 설거지 하는 걸 지켜보며 끝날 때까지 기다림.

3. 다 끝난 후 이제 빨래 하는 방법을 가르쳐주려고 함.

위와 같이 진행이 되었습니다. 즉 엄마는 아들이 설거지를 끝낼 때까지 기다렸습니다. 그리고 끝나서야 다른 작업을 하기 시작합니다. 이게 동기입니다. 그렇다면 비동기 callback은 무엇일까요? 대충 감이 오시죠?

◆ 비동기적 callback 함수

호출을 하였으면 호출에 대한 응답을 기다리지 않고 다음 작업을 하는 것입니다. 아래 코드를 확인해봅시다. (코드는 3초 후 설거지를 실행하는 것이지만, 3초 동안 설거지를 한다고 가정합시다.)

// 비동기 callback / async callback
const asyncDishWashing = (dishWashing, delay) => setTimeout(dishWashing, delay);

console.log("아들아 설거지 해라");
// 엄마가 아들 설거지 하는 걸 지켜보는 것이 아니라 다른 일 하는 중
asyncDishWashing(() => console.log("설거지 다 했어"), 3000);
// 엄마가 아들 설거지 하는 걸 지켜보는 것이 아니라 다른 일 하는 중
console.log("엄마는 빨래하면서 기다릴게 끝나면 말해.");
비동기적 callback

비동기는 위 동기 예시를 비동기로 변경하여 작성하였습니다.

1. 엄마가 아들에게 설거지 명령

아들이 설거지 중 ...

2. 엄마가 아들이 설거지 하는 동안 빨래 함

아들이 설거지 중 ...

3. 아들이 설거지 끝난 다음 엄마에게 끝나다고 말을 함.

위와 같이 진행이 되었습니다. 즉 엄마는 아들에게 설거지를 시킨 후 기다리지 않고 빨래를 하였습니다. 여기서 기다리지 않고 다른 작업을 하는 것비동기입니다. 그렇다면 span.bold_line callback 지옥은 무엇일까요? 한번 알아봅시다.

callback 지옥

2개의 eat, brushTeeth 함수가 있습니다. 각각의 함수에 대해 알아보겠습니다.

◆ eat 함수

초밥, 고기 2개만 먹을 수 있습니다. 다른 것은 먹지 않습니다.

◆ brushTeeth 함수

밥을 먹은 후 실행하는데 초밥을 먹은 경우는 기분 좋게 막고 양치를 하지만, 고기를 먹는 경우는 항상 체해 양치를 하지 못 합니다.

위 함수의 설명대로 구현해보았습니다.

// callback 지옥 / callback hell
const eat = (food, onSuccess, onError) => {
  setTimeout(() => {
    if (food === "초밥" || food === "고기") onSuccess(food);
    else onError(new Error("초밥, 고기 외 먹지 않습니다."));
  }, 1000);
};

const brushTeeth = (food, onSuccess, onError) => {
  setTimeout(() => {
    if (food === "초밥") onSuccess(`${food}을(를) 맛있게 먹고 양치까지 끝`);
    else onError(`${food}를 먹고 체해서 양치를 하지 못 했음`);
  }, 1000);
};

코드는 어렵지 않죠? 함수별로 확인해보겠습니다.

◆ eat 함수

food가 초밥 or 고기인 경우 onSuccess callback 함수에 food를 넘겨줍니다. 그 외인 경우 onError callback 함수에 Error 객체를 넣어줍니다.

◆ brushTeeth 함수

food가 초밥인 경우 onSuccess callback 함수를 호출합니다. 그 외인 경우 onError callback 함수에 Error 객체를 넣어줍니다.

함수는 위와 같이 매우 간단합니다. 그럼 실행해보겠습니다.

◆ 초밥으로 실행하기

성공 이미지

결과는 우리의 예상대로 아주 맛있게 먹고 양치까지 끝났다고 합니다.

◆ 고기로 실행하기

실패 이미지 - 1

고기를 먹었으므로 체해 error 처리가 되었습니다.

◆ 샌드위치로 실행하기

실패 이미지 - 2

초밥이나 고기가 아니가 때문에 error 처리가 되었습니다.

어떤가요? 코드에 문제가 있어보이나요? 전혀 문제가 없어보입니다. 그러나 만약 양치한 후 특정 조건에 맞게 세수 -> 샤워 -> 몸 닦기 -> 로션 바르기 등등 을 계속한다고 하면 어떻게 될까요? 아래 코드를 확인해봅시다.

// callback 지옥 코드
eat(
  "밥먹기",
  () => {
    brushTeeth(
      "양차히기",
      () => {
        washingFace(
          "세수하기",
          () => {},
          () => {
            console.log("에러 처리");
          }
        );
      },
      () => {
        console.log("에러 처리");
      }
    );
  },
  () => {
    console.log("에러 처리");
  }
);

고작 세수하기까지 구현했는데 벌써 가독성이 매우 안좋습니다. 그리고 에러가 난 경우 어디서 났는 지 확인하기도 어려워 에러 핸들링이 어렵습니다. 또한 정상적으로 모든 코드를 구현한다고 해도 유지보수가 어렵습니다. 이렇게 callback에서 callback을 호출하고 또 callback을 호출 ... 하는 것을 callback 지옥이라고 합니다. 그럼 이 지옥을 어떻게 하면 탈출할 수 있을까요? 다음 게시글에서 promise를 배워 지옥을 탈출해봅시다.

코드 자세히 확인해보기

마지막

해당 내용은 틀릴 수도 있습니다. 틀린 내용이 있으면 조언 부탁드립니다.

반응형