Callback

100pearlcent·2021년 11월 6일
0

JavaScript

목록 보기
19/22
post-thumbnail

JS 호스트 환경이 제공하는 여러 함수를 사용하면 비동기 동작을 스케줄링 할 수 있다

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



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

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



📝 loadScript(src) 사용법

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

이 때 스크립트는 비동기적으로 실행된다 👉 로딩은 당장 시작되더라도 실행은 함수가 끝난 후에야 되기 때문


loadScript('/my/script.js');

// loadScript 아래의 코드는
// 스크립트 로딩이 끝날 때까지 기다리지 않는다
// ...

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


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

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(); // 제대로 동작
  ...
});

이처럼 무언가를 비동기적으로 수행하는 함수는 함수 내 동작이 모두 처리된 후 실행되어야 하는 함수가 들어갈 콜백을 인수로 반드시 제공해야 한다



콜백 속 콜백

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

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

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

  })

});

콜백 안에 콜백을 넣는 것은 수행하려는 동작이 단 몇 개 뿐이라면 괜찮지만, 동작이 여러개인 경우엔 좋지 않다



에러 핸들링

스크립트 로딩에 실패했을 경우를 고려하여 로딩 에러를 추적할 수 있게 기능을 개선하자면

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 {
    // 스크립트 로딩이 성공적으로 끝남
  }
});

loadScript는 스크립트 로딩에 성공하면 callback(null, script)을, 실패하면 callback(error)을 호출한다
👉 이런 패턴을 오류 우선 콜백(error-first callback) 이라고 한다


- 오류 우선 콜백

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

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

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



콜백 지옥

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를 이용해서 개선시킬 필요가 있다

0개의 댓글