DEVELOPMENT/JavaScript

[JS] 콜백 (Callback)

Watnu 2025. 2. 20. 10:13



01


콜백 (Callback) 이란?

자바스크립트는 비동기적으로 작동하며, 싱글 스레드에서 한 번에 하나의 작업만 처리할 수 있습니다.

이때 콜백은 다른 함수가 실행을 마친 후 실행되는 함수입니다.

콜백을 사용하면 비동기 작업이나 특정 작업이 끝난 후 결과를 받아서 다음 작업을 실행할 수 있습니다.

 

 

return과 Callback 은 다른 건가요?

: 네 return과 콜백함수는 다른 개념입니다.

 

return

- 함수에서 값을 반환하는 데 사용되는 키워드입니다.

- 함수에서 값을 반환하는 키워드로, 함수 실행을 종료하고 을 반환하여 다른 곳에서 사용할 수 있게 합니다.

 

콜백 함수 (Callback)

- 다른 함수에 인자로 전달되어 나중에 호출되는 함수입니다.

- 콜백 함수는 다른 함수에 전달되어, 해당 함수 내에서 나중에 호출되는 함수입니다.

 

따라서 return은 콜백 함수와 관계가 없고, 두 개는 다른 개념입니다.



02


Callback이 필요한 이유

콜백 함수를 사용하면 작업들이 순차적으로 실행됩니다.

예를 들어, 아래와 같이 콜백을 사용하면 하나의 작업이 끝난 후, 그 결과를 받아서 다음 작업을 진행할 수 있습니다.

function myMenu(callback) {
  const menu = "불닭볶음면";
  callback(menu);
}

function orderFood(menu, callback) {
  console.log("오늘의 점심 메뉴는", menu);
  callback();
}

function resultOrder() {
  console.log("세팅 완료!");
}

myMenu(function (menu) {
  orderFood(menu, function () {
    resultOrder();
  });
});
//결과:
//오늘의 점심 메뉴는 불닭볶음면
//세팅 완료!

 

만약 orederFood 함수에 callback을 주석처리 하게 된다면..

orderFood 함수가 콜백을 호출하기 전에 실행을 멈추지 않으므로,

resultOrder가 호출되지 않습니다. 콜백을 제대로 전달하지 않으면 실행 순서가 흐트러지게 됩니다.



03


Callback 만들어보기

아래처럼 Callback을 할 경우에는 순서대로 출력이 됩니다

function watchMovie(movie, callback) {
  alert(`${movie} 영화를 시작합니다.`);
  callback();
}

function movieFinished() {
  alert('영화 시청이 끝났습니다.');
}

watchMovie('인셉션', movieFinished);
//결과:
//인셉션 영화를 시작합니다.
//영화 시청이 끝났습니다.


04


콜백 속 콜백

스크립트가 두 개 잇는 경우, 첫 번째 스크립트가 종료되고 두 번째 스크립트를 어떻게 순차적으로 불러올 수 있을까요

 

가장 자연스러운 방법으로는 아래코드처럼 콜백함수 안에 두 번째 함수를 호출하는 것입니다.

이렇게 하면 바깥의 MyMenu 함수가 실행이 완료된 후, 안쪽 orderFood 함수가 실행됩니다.

function myMenu(callback) {
  const menu = "불닭볶음면";
  callback(menu);
}

function orderFood(menu) {
  console.log("오늘의 점심 메뉴는", menu);
}

myMenu(function (menu) {
  orderFood(menu);
});

 

여기서 스크립트를 하나 더 불러오고 싶다면, 콜백 안에 콜백을 넣어 수행합니다.

콜백 안에 콜백을 넣는 동작은 단 몇 개뿐이라면 괜찮지만,

가독성이 떨어지고, 코드가 복잡해지므로 이런 방식을 자주 사용하는 것은 좋지 않습니다.

function myMenu(callback) {
  const menu = "불닭볶음면";
  callback(menu);
}

function orderFood(menu, callback) {
  console.log("오늘의 점심 메뉴는", menu);
  callback();
}

function resultOrder() {
  console.log("세팅 완료!");
}

myMenu(function (menu) {
  orderFood(menu, function () {
    resultOrder();
  });
});


05


에러 핸들링

지금까지의 예제에서는 데이터 로딩 실패나 에러 상황에 대한 처리가 없습니다!

언제나 우리는 로딩이 실패할 가능성을 고려해야 합니다. 

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생했습니다.`));

  document.head.append(script);
}

loadScript는 스크립트 로딩에 성공하면 callback(null, script)을, 실패하면 callback(error)을 호출합니다.

 

에러 우선 콜백 (Error-First Callback)

이 방식은 오류가 발생했을 때 첫 번째 인자에 오류 객체를 전달하고,

오류가 없다면 첫 번째 인자는 null로 두고, 두 번째 인자에 결과를 전달합니다.

loadScript('/my/script.js', function(error, script) {
  if (error) {
    // 에러 처리
  } else {
    // 스크립트 로딩이 성공적으로 끝남
  }
});

 

오류 우선 콜백 스타일을 사용하면, 단일 콜백 함수에서 에러 케이스와 성공 케이스 모두를 처리할 수 있습니다



06


콜백지옥: 멸망의 피라미드

콜백 기반 비동기 처리는 꽤 쓸만해 보이기도 하고 실제로도 그렇습니다. 

하지만 꼬리에 꼬리를 무는 동작들이 많아진다면 " 콜백지옥 "을 경험할 수 있습니다 👿

 

호출이 계속 중첩되면서 코드가 깊어지면서 만들어 내는 패턴을

" 콜백 지옥(Callback Hell) " 혹은 " 멸망의 피라미드(Pyramid of Doom) "라고 부릅니다.

loadScript('1.js', function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript('3.js', function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // 모든 스크립트가 로딩된 후, 실행 흐름이 이어집니다. (*)
          }
        });

      }
    })
  }
});

 

이런 코딩방식은 좋지 않으므로 길어질 것 같다면 독립적인 함수로 분리해서 사용하는 방식도 있습니다.

하지만 이 방식도 코드가 여러 개로 쪼개지기 때문에 가독성이 좋지 않아 집니다..

loadScript('1.js', step1);

function step1(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', step2);
  }
}

function step2(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('3.js', step3);
  }
}

function step3(error, script) {
  if (error) {
    handleError(error);
  } else {
    // 모든 스크립트가 로딩되면 다른 동작을 수행합니다. (*)
  }
};

 

이러한 콜백지옥(멸망의 피라미드) 문제를 해결하기 위해 나타난 가장 좋은 방법이 Promise입니다.



07


마무리

콜백 함수는 비동기 처리를 효율적으로 관리할 수 있지만, 중첩이 많아지면 코드가 복잡해지고 가독성이 떨어집니다.

이러한 문제를 해결하기 위해 Promise나 async/await을 활용하면 더 깔끔하고 직관적인 코드를 작성할 수 있습니다.

 

 

👉 Promise 정리 포스팅 바로가기

 

[JS] Promise

01Promise자바스크립트에서 비동기 작업이 끝날 때, 그 결과를 알려주는 "약속"과 같은 역할의 오브젝트입니다.예를 들어, 어떤 일이 잘 처리되면 결과를 알려주고, 문제가 생기면 에러를 알려주는

dev-watnu.tistory.com