Skip to content

Latest commit

 

History

History
126 lines (89 loc) · 7.06 KB

B_Callback.md

File metadata and controls

126 lines (89 loc) · 7.06 KB

Callback Hell

콜백은 단일스레드 환경(이벤트 루프 큐가 단일 쓰레드)인 자바스크립트에서는 필수 불가결이라고 생각한다. 이유를 설명하는 EventLoop

그런데 이 콜백 때문에 발생될 수 있는 버그가 너무 많고 그 버그를 최근에는 Promise 를 통해 해결한다.

이 콜백이 어떤 문제가 있는지 먼저 살펴본 뒤 다음에는 프로미스가 뭔지도 알아보자.

콜백은 어렵다.

콜백은 사실 어렵지 않다.

//A
ajax('...', function thisIsCallBack() {
  //C
});
//B

어렵지 않다. 코드가 '지금' 실행되는 것이 아닌 '나중'에 실행되는 코드다. 그런데 코드가 길어지고 콜백에 콜백에 콜백을 넘어가기 시작하면 지옥에 빠지게 된다.

멀티태스킹이 아닌 콘텍스트

인간의 뇌는 기본적으로 싱글쓰레드의 빠른 context 교환기다. 멀티태스킹이 불가능하다. 전화를 받으면서 수학문제를 푼다면 얼핏보기에는 가능할 수 있지만 사실은 전화를 할때와 문제를 풀때는 재빠르게 뇌가 실행기를 움직이며 작동할 뿐이다. 멀티 쓰레딩처럼 따로 움직일 수는 없다.

그래서 백그라운드에 옮겨저 '나중'에 실행되는 콜백과 같은 비동기적 흐름은 인간의 뇌의 flow 와 다르다. 그래서 쉽게 이해가 되지 않는다.

단순히 이해만 안되는게 아니다

listen('click', function handler(evt) {
  setTimeout(function request() {
    ajax('http://....', function response(text) {
      if (text === 'hello') {
        handler();
      } else if (text === 'world') {
        request();
      }
    });
  }, 500);
});

이러한 코드는 조금 연습하면 따라 갈 수 있다. 그런데 이 경우 다양한 상황의 에러처리가 안 되어있다. 만일 클릭 이벤트를 통해 ajax()의 url 에서 어떤 작업을하는 중에 또 다시 클릭을 누른다면?? setTimeout 도중 다른 noise 가 발생한다면? 이건 단순한 코드라 몇가지 안되겠지만 코드가 더 복잡하다면?? 더 예를들어 콜백 사이에 다른 콜백이 들어가고 이벤트 루프에 복수의 콜백이 동시에 들어가거나, 분기(경합, 걸쇠)(이건 추후에 설명)를 해야한다거나?? 모든 경우의 수를 따져 코딩하게된다면 엄청난 하드 코딩이 필요할 것이며 코드를 관리하기가 정말 너무나도 힘들게 된다.

믿음성

위 내용까지 콜백이 인간의 뇌와는 흐름이 달라 힘들기 어려우며, 경우의 수도 너무 많기 때문에 관리가 힘듦을 보았다. 그렇지만 사실 콜백의 가장 치명적인 문제는 이해가 힘든 것도, 관리가 잘 되기 힘든 것도 아니다. 믿음성의 문제다.

제어권(control)

//A
ajax('...', function thisIsCallBack(data) {
  //C
});
//B

다시 이 코드를 보자. 이 코드는 프로그램이 진행중에 ajax 가 실행완료가 되면 thisIsCallBack 을 큐에 넣으라는 의미다. 여기서 ajax 는 콜스택에 밀어넣는 이벤트 루프의 행위로 콜스택의 제어권(control)을 가지게 된다. 이게 중요하다. 제어권. 이를 제어의 역전Inversion of Control이라고 한다.

이 제어권이 넘어가게 되면 믿음의 문제가 생긴다. 내가 실행하는 환경의 call stack을 누군가가 임의로 제어할 수 있다는 것이다.

예를 들어보자. ajax 를 통해 API를 호출한다. 이 API는 내가 작성한 코드가 아니며 서드파티 유틸리티거나 누군가 만들어 놓은 라이브러리를 이용해 data 만 받는다. 위의 코드처럼 비동기 호출을 하게되면 이 외부 라이브러리가 내 콜스택을 조절할 수 있는 제어권을 갖게된다. 그게 문제다.

콜백지옥

YOU DON'T KNOW JS 의 예시를 빌려오자. 현재 상황은 쇼핑몰 전자상거래 결제 시스템이다. 고객이 확인 버튼을 누르면 어떤 분석 추적 솔루션의 함수를 호출해서 구매정보를 추적한다고 하자. 추적이 완료되면 결제가 승인되며, 결제완료 페이지로 넘어간다. 의사코드를 보자면 아래와 같다.

solutionUtil.trackPurchase(purchaseData, function CBF() {
  // 결제데이터를 넘긴 뒤
  // 비동기적인 처리가완료되면 콜백실행
  assignCredit(); // 결제
  goThanksPage(); // 페이지 이동
});

흔히 볼 수 있는 코드다.

그런데 저 유틸리티가 어떤 특정 조건에서는 초당 한번씩 받아온 콜백(CBF())을 호출한다고 하자. 제어권이 넘어가 콜백함수를 실행시키는것이 우리의 프로그램이 아니라 백그라운드의 저 함수다. 그런데 만일 저 유틸리티가 콜백을 잘못 호출하게끔 코드를 변경했다고 생각해보자. 내 문제는 없다. 다만 저 유틸이 잘못되어 고객이 5 번 결제가 되었다고 한다. 끔찍하다. 그냥 유틸리티를 제공한 솔루션회사에게 책임을 맡기고 업체를 다른곳으로 바꾸면 될까?? 애초에 잠재적인 버그가 있는 코드라면 없애야 맞다.

var tracked = false;

solutionUtil.trackPurchase(purchaseData, function CBF() {
  if (!tracked) {
    tracked = true;
    assignCredit();
    goThanksPage();
  }
});

때문에 관문을 만들었다. 한번 이상은 실행되지 않는다. 근데 만일 또 코드가 잘못되어 한번도 호출하지 않는다면? 그 이외에도 콜백을 너무 일찍 부르거나, 늦게 부르거나, 많이부르거나 에러(exception) 발생이라던가 데이터가 비정상이라던가... 모든 예외의 경우를 모두 처리해줄 것인가? 그럼 또 하드코딩인데? 콜백 지옥에 빠지게 된다.

큰 문제점은 믿음

중요한 점은 외부의 비동기적 코드를 100% 믿을 수 없다는 점이다. 그런데 그 못 믿을 코드에게 내 콜스택을 움직일 수 있는 제어권을 넘겨준다?? 분명히 버그가 발생할 수 있다.

꼭 외부의 코드만 문제가 되는 것은 아니다. 내가 짠 코드도 예상치 못한 곳에서 잠재적인 버그를 가질 수 있다. 그런데 문제는 이 제어권을 그 못믿는 코드에게 줄 수 밖에 없는 디자인이라는 것이다.

ES5 에서의 해결

두가지 방법을 혼용해서 "방지"할 수는 있다.

function success(data) {
  console.log(data);
}
function failure(err) {
  console.log(err);
}
var a = 0;

ajax('url...', asyncify(success(data)), failure(err));

asyncify()에는 비동기를 동기화 시켜서 success 를 실행하고 혹시 ajax 의 결과가 에러면 failure 를 실행시킨다. 이러면 에러처리가 우선 해결된다. 그리고 asyncify 를 통해서 일시적으로 동기화를 시켜 비동기 결과가 아직 도착한 뒤 콜백을 실행시키게 만들 수는 있다. 그런데 이를 위해 저 asyncify 라는 함수를 만들어야 하고 거기서 에러는 없는지 테스트 해보고... 점점 무거워질 것이다.

이를 해결하기 위해 ES6 에서 등장한것이 바로 Promise 다. Promise1.md