4 콜백 함수

콜백 함수

콜백 함수란 다른 함수의 인자로 전달되는 함수를 말한다.

이렇게 다른 함수에 함수를 함께 전달해서, 특정한 상황에서 그 함수를 실행해달라 요청하는 것이다. 그 함수의 제어권을 넘긴다고 할 수 있다.

제어권

  1. 콜백 함수의 제어권을 받은 코드는 콜백 함수 호출 시점에 대한 제어권을 갖는다. 그 콜백 함수를 언제 호출할 것이냐는 전적으로 제어권을 받은 코드에 달려 있다.
  2. 콜백 함수에 인자를 넣어서 호출하는 쪽은 콜백 함수의 제어권을 받은 쪽이다. 따라서 인자 순서에 대한 제어권도 갖는다. (마음대로 map에 전달하는 콜백 함수 인자를 (index, value)로 변경해서는 안 된다.)

콜백 함수와 this

콜백 함수를 받는 함수들 중에는 thisArg 같은 인자로 콜백 함수를 실행할 때 적용할 this를 함께 인자로 받는 함수들이 있다. (참고 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#iterative_methods) 이런 함수들은 내부에서 call/apply 메서드를 이용해서 thisArg로 받은 값이 있다면 그 값을, 아니면 전역객체를 바인딩하는 식으로 동작한다.

콜백 함수는 어떤 함수를 전달하더라도 함수로서 호출된다. 메서드를 전달하더라도 받는 입장에서는 그 프로퍼티에 담긴 함수 값을 받은 것이 된다. 따라서 메서드를 전달하든, 일반 함수를 전달하든 기본적으로 함수로서 호출된다.

그렇다면 만약에 thisArg를 받지 않는 함수의 경우에는 어떻게 this를 바인딩할 수 있을까? 두 가지 방법이 있다.

첫 번째는 전통적 방법이다. this를 다른 변수에 담아서 콜백 함수에서 this 대신 그 변수를 사용하게 하고, 이걸 클로저로 만드는 방식이다.

var obj1 = {
  name: 'obj1',
  func: function() {
    var self = this; // obj
    return function () {
      console.log(self.name); // this 대신 self를 사용한다.
    }
  }
}
var callback = obj1.func();
setTimeout(callback, 1000); // obj1 출력

근데 이 방식은 번거롭기도 하고 메모리를 낭비한다는 단점이 있다.

그래서 ES5에서 이 단점을 해결할 수 있는 두 번째 방법, bind 메서드가 등장했다. this를 따로 저장할 필요도 없고 재사용도 용이하다.

var obj1 = {
  name: 'obj1',
  func: function() {
    return function () {
      console.log(this.name);
    }
  }
}
setTimeout(obj1.func.bind(obj1), 1000); // this에 obj1을 명시적으로 연결

var obj2 = {name: 'obj2'};
setTimeout(obj1.func.bind(obj2), 1000); // 재사용도 가능

콜백 지옥 / 비동기

콜백이 중첩되어서 가독성이 떨어지고 에러 처리가 곤란하고 코드 수정도 어려워지는 문제 상황을 콜백 지옥이라고 한다. 주로 비동기 작업을 수행할 때 콜백을 익명 함수로 전달하는 과정이 반복되면서 발생하는 현상이다.

이 문제를 해결하기 위한 방법이 지속적으로 등장했는데 ES6에서는 Promise와 Generator, ES2017에서는 async/await이 등장했다. (책에서는 간단히 소개하는 것으로 마무리됐다.)