Javascript/Node.js

콜백

Frankie 2021. 9. 27. 16:42

자바스크립트 호스트 환경이 제공하는 여러 함수를 사용하면 비동기 동작을 스케줄링 할 수 있습니다. 원하는 때에 동작이 시작하도록 할 수 있습니다.

setTimeout은 스케줄링에 사용되는 가장 대표적인 함수입니다.

실무에서 맞닥뜨리는 비동기 동작은 아주 다양합니다. 스크립트나 모듈을 로딩하는 것 또한 비동기 동작입니다.

 

src에 있는 스크립트를 읽어오는 함수 loadScript(src)를 예시로 비동기 동작 처리가 어떻게 일어나는지 살펴보면

function loadScript(src) {
	// <script> 태그를 만들고 페이지에 태그를 추가합니다.
    // 태그가 페이지에 추가되면 src에 있는 스크립트를 로딩하고 실행합니다.
    let script = document.createElement('script');
    script.src = src;
    document.head.append(script);
}

위 함수는 <script src="...">를 동적으로 만들고 이를 문서에 추가합니다. 브라우저는 자동으로 태그에 있는 스크립트를 불러오고, 로딩이 완료되면 스크립트를 실행합니다.

loadScript(src) 사용법은

// 해당 경로에 위치한 스크립트를 불러오고 실행함
loadScript('/my/script.js');

이고 이 때 스크립트는 '비동기적으로' 실행됩니다. 로딩은 당장 되어도 실행은 함수가 끝난 후에야 되기 때문입니다.

따라서 loadScript(...) 아래에 있는 코드들은 스크립트 로딩이 종료되는 걸 기다리지 않습니다.

loadScript('/my/script.js'); // script.js엔 "function newFunction() {…}"이 있습니다.

newFunction(); // 함수가 존재하지 않는다는 에러가 발생합니다!

loadScript(...)를 호출하자마자 내부 함수를 호출하면 원하는 대로 작동하지 않습니다.

그런데 현재로서 함수 loadScript에서 스크립트 로딩이 완료되었는지 알 방법이 없는데 원하는 대로 스크립트 안의 함수나 변수를 사용하려면 스크립트 로딩이 끝났는지 여부를 알 수 있어야 합니다.

loadScript의 두 번째 인수로 스크립트 로딩이 끝난 후 실행될 함수인 콜백(callback) 함수를 추가해보면

function loadScript(src, callback) {
	let script = document.createElement('script');
    script.src = src;
    
    script.onload = () => callback(script);
    
    document.head.append(script);
 }

이렇게 되고 새롭게 불러온 스크립트에 있는 함수를 콜백 함수 안에서 호출하면 원하는 대로 외부 스크립트 안의 함수를 사용할 수 있습니다.

loadScript('/my/script.js', function(){
	// 콜백 함수는 스크립트 로드가 끝나면 실행된다.
	newFunction(); // 이제 함수 호출이 제대로 동작
    ...
});

 

콜백 속 콜백

스크립트가 두 개 있는 경우, 두 스크립트를 순차적으로 불러오려면 아래와 같이 콜백 함수 안에서 두 번째 loadScript를 호출하면 됩니다.

loadScript('/my/script.js', function(script) {
	alert(`${script.src}을 로딩했습니다. 이제 다음 스크립트를 로딩하세요.`);
    
    loadScript('/my/script2.js', function(script){
    	alert(`두 번째 스크립트를 로딩했습니다.`);
    });
 });

 

에러 핸들링

loadScript에서 로딩 에러를 추적할 수 있게 기능을 개선해보면 

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) 을 호출합니다.

개선된 loadScript의 사용법은 다음과 같다.

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

이렇게 에러 처리하는 방법은 흔히 사용되는 패턴이다. 이런 패턴을 '오류 우선 콜백'이라고 부른다.

오류 우선 콜백은 다음의 관례를 따른다.

1. callback의 첫 번째 인수는 에러를 위해 남겨둔다. 에러가 발생하면 이 인수를 이용해 callback(err)이 호출된다.

2. 두 번째 인수는 에러가 발생하지 않았을 때를 위해 남겨둔다. 원하는 동작이 성공한 경우엔 callback(null, result1, result2...)이 호출된다.

 

콜백 지옥

꼬리에 꼬리를 무는 비동기 동작이 많아지면 깊은 중첩 코드가 되어서 소위 '콜백 지옥' 혹은 '멸망의 피라미드'라는 패턴이 됩니다.

 

-> 콜백 지옥을 피하는 가장 좋은 방법 중 하나는 promise를 사용하는 것이다.

'Javascript > Node.js' 카테고리의 다른 글

모듈 시스템  (0) 2021.09.30
promise API  (0) 2021.09.28
커스텀 에러와 에러 확장  (0) 2021.09.27
try..catch와 에러 핸들링  (2) 2021.09.27
메서드와 this  (0) 2021.09.27