티스토리 뷰

11.1 Introduction: callbacks

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

// ------------

loadScript('/my/script.js');
// loadScript 아래의 코드는
// 스크립트 로딩이 끝날 때까지 기다리지 않습니다.
// ...

// ------------

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

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

스크립트는 비동기적으로 실행된다. 로딩은 바로 시작되는데, 실행은 함수가 끝난 후여야 한다. 따라서, loadScript 이후에 다른 함수가 수행되려하는데, 해당 함수가 아직 로드되지 않았다면 어떻게 할까? 콜백함수를 파라미터로 추가하여 해결할 수 있다. 콜백함수 안에서 로드될 함수를 사용하면 동작한다.

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(); // 이제 함수 호출이 제대로 동작합니다.
  ...
});

이런식으로 인수로 콜백 함수를 넘겨서 해당 함수의 동작이 끝나고 난뒤 수행되도록 프로그래밍 하는 것을 콜백 기반 비동기 프로그래밍이라고 한다. 비동기적으로 수행하는 함수는 함수 내 동작이 모두 처리된 후 실행되어야 하는 함수가 들어갈 콜백을 무조건 인수로 제공해야한다.

실제 스크립트 예시

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

loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
  alert(`${script.src}가 로드되었습니다.`);
  alert( _ ); // 스크립트에 정의된 함수
});

Callback in callback

그렇다면 특정 스크립트를 순차적으로 여러번 불러오고 싶을 때, 이를 위해 콜백함수를 쓸 수 있겠다.

loadScript('/my/script.js', function(script) {

  loadScript('/my/script2.js', function(script) {

    loadScript('/my/script3.js', function(script) {
      // 세 스크립트 로딩이 끝난 후 실행됨
    });

  })

});

하지만, 콜백함수가 계속해서 늘어날 경우, 별로 좋지 않을 것이 뻔해보인다. 추후 다른 방식에 대해 알아보자.

Handling errors

특정 함수가 수행되는 동안 에러가 발생하면 이를 처리해줄 콜백함수도 필요하다.

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('/my/script.js', function(error, script) {
  if (error) {
    // 에러 처리
  } else {
    // 스크립트 로딩이 성공적으로 끝남
  }
});

이러한 방식을 오류 우선 콜백(error-first callback)이라고 부른다. callback의 첫번째 파라미터는 에러를 위해 남겨 둔다. 에러 발생 시 'callback(err)'로 콜백함수를 호출하면 된다. 만약 에러가 발생하지 않았다면, 원하는 동작에 맞게 callback(null, result1, result2...) 호출 하면된다.

Pyramid of Doom

콜백 기반 비동기 처리를 자주 사용하다보면, 아래와 같은 쉽지않은 상황이 펼쳐진다. (예전에 javascript 공부하다가 포기했던 부분이다...)

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 {
    // 모든 스크립트가 로딩되면 다른 동작을 수행합니다. (*)
  }
};

 

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday