본문 바로가기
공부/JS

[Javascript] 콜백 함수 알아보기

by Piva 2024. 1. 21.
  • 코어 자바스크립트를 읽고, 콜백 함수에 대해 공부한 내용을 정리한다.

콜백 함수(Callback Function)

: 다른 코드의 인자로 넘겨주는 함수, 매개변수를 통해 다른 함수의 내부로 전달되는 함수.

※ 고차 함수(Higher Order Function): 매개변수를 통해 함수 외부에서 콜백 함수를 전달받은 함수.

 

  • 콜백 함수를 받은 고차 함수는 원하는 때에 콜백 함수를 실행할 수 있음.
  • 즉, 콜백 함수의 제어권을 다른 함수에 넘겨주는 것.

 

제어권

  고차함수는 콜백 함수에 여러가지 제어권을 가질 수 있다.

 

1. 호출 시점

   고차 함수는 콜백 함수를 자신이 원하는 시점에 실행할 수 있다.

var counter = 0;

var foo = () => {
	console.log(counter);
	if (++counter > 5) clearInterval(timer);
};

var timer = setInterval(foo, 100);

 

  • setInterval은 매개변수로 foo를 받는다. 즉, foo 함수는 setInterval의 콜백 함수이다.
  • setInterval함수는 두번째 매개변수로 주어진 시간마다 콜백 함수를 실행한다.
    • 위 코드에서는, 100ms(0.1초) 마다 foo함수를 실행시킨다.
  • 즉, setInterval함수는 자신의 콜백함수인 foo함수를 원하는 시점에 호출할 수 있는, 호출 시점에 대한 제어권을 갖는다.

 

2. 매개변수

  고차 함수는 콜백 함수에 원하는 인자를 어떤 순서로 넘길 것인지 정할 수 있다.

var arr = [10, 20, 30].map((current, index) => {
    console.log(current, index);
    return current + 10;
});

console.log(arr);

 

  • map함수는 배열 내 모든 원소에 대해 전달받은 콜백 함수를 실행한다. (참고)
  • 따라서 배열 내 모든 원소(10, 20, 30)에 대해 10을 더하는 과정이 실행된다.
  • 이 때, 콜백 함수에 어떤 인자를 전달할 것인지는 map함수에 정해져 있다.
    • 첫 번째 인자는 배열의 요소 중 현재 값이 전달된다.
    • 두 번째 인자로는 현재 값의 인덱스가 전달된다.
    • 위의 예시에는 없지만, 세 번째 인자로는 map의 대상이 되는 배열이 전달된다.

 

3. this

  아직 블로그에서는 다루지 않았지만, 함수에서의 this는 전역 객체를 가리킨다. 다만 콜백 함수의 경우, 고차 함수에서 콜백에 별도로 this를 지정할 경우에는 전역이 아닌 그 대상을 참조하게 된다.

  고차 함수가 콜백 함수에 별도의 this를 할당하는 과정은 콜백을 받는 실제 메서드를 구현해봄으로써 확인해볼 수 있다.

 

Array.prototype.map = (callback, thisArg) => {
    var mappedArr = [];
    for (var i = 0; i < this.length; i++) {
    	var mapped = callback.call(thisArg || window, this[i], i, this);
        mappedArr[i] = mapped;
    }
    return mappedArr;
};
  • Array의 map함수를 직접 구현한 코드이다.
    • 첫 번째 인자로 콜백 함수를 받고, 두 번째 인자로 별도로 지정해줄 this를 받는다.
    • 현재 배열의 각 요소에 대해 콜백 함수를 실행한다. 이때 call함수를 사용하는데, call함수를 통해 thisArg를 받았을 경우 전달받은 this를, 전달받지 않았다면 전역 객체를 바인딩한다.

 

  위의 예제를 통해, 고차 함수가 콜백에 별도의 this를 지정할 수 있는 까닭은 call/apply 등의 메서드를 통해 명시적으로 this를 바인딩하기 때문임을 알 수 있다.

 

※ 콜백 함수는 '함수'로 기능한다. 즉, 콜백 함수로 객체의 메서드를 전달하더라도, 그 메서드는 메서드가 아닌 함수로 호출된다.
→ 따라서 일반적인 메서드와 달리, 콜백으로 전달된 객체의 메서드의 this는 객체가 아닌 전역 객체를 바라본다.

 

 

콜백 함수 내 this에 다른 값 바인딩하기

  • 위에서 언급하였듯, 메서드를 콜백 함수로 전달하면 this가 전역 객체를 바라본다는 문제가 생긴다.
  • 이를 해결하기 위한 한 가지 방법으로, this를 다른 변수에 담아서 콜백 함수에서는 해당 변수를 this 대신 사용하게 하는 방법이 있다.
var obj = {
    name: 'foo',
    func: function () {
    	var self = this;
        return function () {
        	console.log(self.name);
        };
    }
};

var callback = obj.func();
setTimeout(callback, 500);

 

  • self 변수에 객체의 this를 담는다. 그리고 obj 객체의 func 함수에서 this가 아닌 self를 사용한다.
  • 다만 이 방법은 실제 this를 사용하는 방법이 아니기 때문에 부적절하다.
  • ES5부터 추가된 bind 함수를 사용하면 보다 적절하게 this를 바인딩할 수 있다.

 

var obj1 = {
    name: 'foo',
    func: function () {
    	console.log(this.name);
    }
};

setTimeout(obj1.func.bind(obj1), 500); // foo 가 출력된다

 

 

콜백 지옥 & 비동기 제어

콜백 지옥(Callback Hell)

: 콜백 함수가 중첩되며 코드의 들여쓰기가 깊어지는 현상. (이전에 클린 코드 스터디 글에서 다룬 적이 있다)

  • 주로 비동기적인 작업을 수행하기 위해 콜백의 전달이 잦아져 발생한다.
※ 비동기(Asynchronous) 코드: 현재 실행 중인 코드의 완료 여부와 상관 없이 다음 코드로 넘어가는 것. 주로 요청/대기/보류 등의 작업과 관련된다.

 

콜백 지옥을 해결하는 주된 방법인 Async/Await, Promise 등에 대한 것은 앞서 언급한 클린 코드 스터디에서도 다룬 적이 있으므로, 여기선 따로 다루지 기록하지 않을 예정.