JS | 콜백함수는 다시 돌아오는 거야 🪃

자바스크립트를 사용하는 개발자라면 숨쉬듯이 사용하는 콜백 함수. 하지만 정작 콜백 함수가 무엇이냐고 물어본다면 쉬이 대답하기 어렵다. 콜백 함수는 API 통신 등 비동기 로직을 작성할 때 필수적으로 쓰이기 때문에 그 개념을 잘 이해하고 있어야 제대로 코드를 짤 수 있다.


콜백 함수(Callback Function)

콜백(callback)이란 '되돌아 호출한다'는 의미

즉, 함수 A를 호출하면서 특정한 조건이 되면 함수 B를 실행하고 자신에게 알려달라는 요청을 보내는 것.

요청을 받은 함수 A는 해당 조건이 충족되었는지 스스로 판단하고 함수 B를 직접 호출함.

콜백 함수는 다른 코드의 인자로 넘겨줌으로써 '제어권'도 함께 위임한 함수를 가리킴.

콜백 함수를 넘겨받은 코드는 자체적인 내부 로직에 의해 콜백 함수를 적절한 시점에 실행함.

💡 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 '고차 함수(Higher-Order Function)'라고 함. 고차 함수에 콜백 함수를 전달할 때는 함수 자체를 전달해야 함.

콜백 함수는 함수형 프로그래밍 패러다임뿐만 아니라 이벤트 처리, Ajax 통신 등 비동기 처리에 많이 활용되는 패턴임.


콜백 함수의 제어권

호출시점

콜백 함수를 넘겨받은 코드는 콜백 함수의 호출 시점에 대한 제어권을 가짐.

var count = 0;
var timer = setInterval(function() { // setInterval 함수가 콜백 함수의 제어권을 넘겨받음.
	console.log(count);
	if(++count > 4) clearInterval(timer);
}, 1000);

// 1초 후
// 0
// 1초 후
// 1
// 1초 후
// 2
// 1초 후
// 3
// 1초 후
// 4

인자

콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인가에 대한 제어권을 가짐.

콜백 함수를 호출할 때 인자로 넘겨줄 값들과 그 순서가 정해져 있기 때문에 반드시 순서를 따라서 인자를 넘겨주어야 함.

var newArr = [10, 20, 30].map(function(currentValue, index) {
	console.log(currentValue, index);
	return currentValue + 5;
});

console.log(newArr);

// 출력값
// 10 0
// 20 1
// 30 2
// [15, 25, 35]

// ----------------------------------------------------------
// 인자의 순서를 지키지 않은 경우
var newArr2 = [10, 20, 30].map(function(index, currentValue) {
	console.log(index, currentValue);
	return currentValue + 5;
});

console.log(newArr2);

// 출력값
// 10 0
// 20 1
// 30 2
// [5, 6, 7]

this

콜백 함수도 함수이기 때문에 기본적으로 this는 전역 객체를 가리키지만, 제어권을 넘겨받은 코드에서 콜백 함수에 별도로 this가 될 대상을 지정하면 그 대상을 참조함.

콜백 함수 내부에서 this가 다른 값을 참조하도록 하고 싶다면 bind 메서드를 이용하여 this에 그 값을 바인딩함.

setTimeout(function() {
	console.log(this);
}, 1000);

// Window { ... } 출력

document.body.innerHTML += '<button id="btn">Click</button>';
document.body.querySelector('#btn').addEventListener('click', function() {
	console.log(this);
})

// <button id="btn">Click</button> 출력

// bind 메서드로 this 바인딩
var obj1 = {
	name: 'obj1',
	func: function() {
		console.log(this.name);
	}
}
setTimeout(obj1.func.bind(obj1), 1000);

var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 2000);

// 1초 후
// obj1 출력
// 1초 후
// obj2 출력

콜백 함수는 함수다

콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로 호출됨.

var obj = {
	vals: [1, 2, 3],
	logValues: function(value, index) {
		console.log(this, value, index)
	}
}

obj.logValues(1, 2); // { vals: [1, 2, 3], logValues: f} 1 2 출력
[4, 5, 6].forEach(obj.logValues); // Window { ... } 4 0  Window { ... } 5 1  Window { ... } 6 2 출력

배열 고차 함수

콜백 함수는 비동기 처리뿐만 아니라 배열 고차 함수에서도 사용됨.

var map = [1, 2, 3].map(function(item) {
	return item * 2;
});

console.log(map); // [2, 4, 6] 출력

var filter = [1, 2, 3].filter(function(item) {
	return item % 2;
});

console.log(filter); // [1, 3] 출력

var reduce = [1, 2, 3].reduce(function(acc, cur) {
	return acc + cur;
}, 0);

console.log(reduce); // 6 출력

콜백 지옥(Callback Hell)

콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 지나치게 깊어지는 현상

주로 비동기 작업을 수행할 때 발생할 가능성이 높음.

❗️ 가독성이 떨어질 뿐만 아니라 유지보수에도 좋지 않음!

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 {
            // ...
          }
        });
      }
    })
  }
});

콜백 지옥을 해결하는 방법?

  • 익명의 콜백 함수를 '기명 함수'로 모두 변경하는 방법
  • Promise(ES6)
  • Generator(ES6)
  • async/await(ES2017)

좋은 웹페이지 즐겨찾기