본문 바로가기

Language/JavaScript

<자바스크립트> 콜백

1. 콜백

* 소개

 - 실무에서 비동기 동작처리를 할 때 생기는 문제를 먼저 살펴보자.

 

 - 대표적인 비동기 동작으로 스크립트나 모듈을 로딩하는 것이 있다.

 

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

 

 - 위는 src에 있는 스크립트를 불러와서 다큐먼트에 추가하는 함수이다. 이 안에는 우리가 사용하고 싶은 harryFunc라는 함수가 들어있다고 가정하자.

 

loadScript('/harry/script.js'); 
harryFunc();

 

 - 위에서 정의한 함수를 사용하여 스크립트를 불러오고 함수를 실행했다. 그러나 안타깝게도 에러가 발생한다.

 

 - 이유는 브라우저가 스크립트를 읽어올 수 있는 시간이 충분히 확보되지 못했기 때문이다. 물론 언젠가 스크립트 로딩은 완료되겠지만 이미 함수호출은 지나갔다.

 

 - 로딩이 완료된 후 함수나 변수를 사용하려면 콜백함수가 필요하다.

 

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

  script.onload = () => callback(script);

  document.head.append(script);
}

loadScript('/harry/script.js', function() {
  harryFunc();
  //...
});

 

 - loadScript의 두 번째 인수에 콜백함수를 추가했다. 콜백 함수는 스크립트 로드가 끝나면 실행된다.

 

 - 따라서 새롭게 불러올 스크립트의 함수나 변수를 콜백 함수 안에서 호출하면 원하는 대로 외부 스크립트를 사용할 수 있다.

* 콜백 속 콜백

 - 위의 예시에서는 하나의 스크립트를 불러왔다. 만약 여러 개의 스크립트를 순차적으로 불러오려면 어떻게해야할까.

 

loadScript('/harry/script.js', function(script) {
  loadScript('/harry/script2.js', function(script) {
    loadScript('/harry/script3.js', function(script) {
      // ...
    });
  })
});

 

 - 위와 같이 중첩 콜백을 만들면 바깥에 위치한 loadScript가 완료된 후, 안쪽으로 하나씩들어갈 것이다.

 

 - 그러나 위처럼 콜백이 중첩되면 불러올 스크립트가 늘어날수록 오른쪽으로 길어진다. 이 문제는 3.콜백 지옥 에서 다루자.

 

2. 에러 핸들링 

 - 위에서 loadScript는 에러를 전혀 처리할 수 없다. 하지만 브라우저에서 로딩은 얼마든지 실패할 수 있으므로 콜백 함수에 에러 핸들링 기능을 추가해보자.

 

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

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error('로딩 실패!'));

  document.head.append(script);
}

 

 - 위와 같이 작성하면 loadScript가 로딩성공시 callback(null, script)을 호출하게 된다. 실패하면 callback(error)을 호출한다.

 

loadScript('/harry/script.js', function(error, script) {
  if (error) {
    // 에러 처리
  } else {
    // 로딩 성공
    // ...
  }
});

 

 - 위와 같이 에러를 처리하는 패턴을 '오류 우선 콜백(error-first-callback)'이라 부른다.

 

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

 

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

   2. 두 번째 이후 인자는 에러가 발생하지 않을 때 사용하기 위한 인자이다.

 

3. 콜백 지옥

 - 위에서 콜백이 중첩될수록 오른쪽으로 길어지는 문제가 있다고 했다. 에러처리까지 있다면 더 길어질 것이다.

 

loadScript('/harry/script.js', function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('/harry/script2.js', function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript('/harry/script3.js', function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // ...
          }
        });

      }
    })
  }
});

 

 - 위를 살펴보면 이렇다. script.js를 로드한다. 에러가 없으면 script2.js를 로드한다. 에러가 없으면 script3.js를 로드한다.

 

 - 호출이 계속 중첩되면서 코드가 깊어지고 있다. 여기서 반복문이나 조건문이 들어가면 관리가 너무 힘들 것이다.

 

 - 위처럼 깊은 중첩 코드가 만들어내는 패턴을 소위 '콜백 지옥(callback hell)' 또는 '멸망의 피라미드(pyramid of doom)'이라 한다.

 

 - 위와 같은 코딩 방식을 피하기 위해 각 동작을 독립적인 함수로 만들어 문제를 해결해보자.

 

loadScript('/harry/script.js', step1);

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

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

function step3(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
  }
};

 

 - 위처럼 작성하면 중첩없이 콜백 기반 스타일 코드와 동일하게 동작하게 할 수 있다.

 

 - 그러나 위 코드는 코드를 읽으려면 이리저리 움직여서 읽어야하고, 각 step함수는 콜백지옥을 피하기위한 용도로만 만들어져서 재사용도 불가능하다.

 

 - 위 문제를 해결하기위한 가장 좋은 방법은 다음 챕터의 '프라미스'이다.

 

 


 

 

참고

 

 

 

모던 자바스크립트 튜토리얼

 

모던 JavaScript 튜토리얼

 

ko.javascript.info