콜백함수 🗣️
들어가기 전에
mdn에 보면 흔히 사용하는 map(), setTimeout() 등의 첫번째 인자는 callbackFn으로 기입되어 있으며, 콜백함수를 의미한다.
이 포스팅을 통해 다음을 이해할 수 있다.
1. 콜백함수란 무엇인가?
2. 콜백함수의 this
3. 콜백 지옥?
콜백함수?🔥
다른 코드에게 인자로 넘겨줌으로써 그 제어권도 함께 위임하는 함수
let count = 0;
let cbFunc = function() {
console.log(count);
if(++count > 4) clearInterval(timer);
}
let timer = setInterval(cbFunc, 5000);
코드를 실행해보면 setInterval에 전달한 첫번째 인자인 cbFunc(콜백함수)는 5초마다 자동으로 실행될 것이다.
콜백 함수 내부에서 count 값을 출력하고 count를 1씩 증가시킨 다음, 그 값이 4보다 크다면 반복 실행을 종료할 것이다.
즉, setInterval이라고 하는 다른 코드에 첫 번째 인자로서 cbFunc 함수를 넘겨주자 제어권을 넘겨받은 setInterval이 스스로의 판단에 따라 적잘한 시점(5초마다)에 함수를 실행했다.
이를 통해, 콜백 함수를 넘겨받은 코드는 콜백 함수를 호출하는 제어권을 가지는 걸 알 수 있다.
실행 코드 | 호출 주체 | 제어권 |
cbFunc(); | 사용자 | 사용자 |
setInterval(cbFunc, 5000); | setInterval | setInterval |
this
- 기본적으로 전역객체를 참조
- 제어권을 넘겨 받은 코드에서 콜백함수에 별도로 this를 지정하면, 그 대상을 참조
[1, 2, 3].forEach(function (){
console.log(this); // 전역객체
});
document.getElementById("btn").addEventListener("click", function () {
console.log(this); // #btn 요소
});
첫 번째 콜백함수의 this는 일반 함수에서 사용되었기에 전역객체(Window)를 출력하게 된다.
하지만, 두 번째 this는 btn 요소를 출력하게 된다.
그 이유는 addEventListener는 내부적으로 this를 이벤트가 발생한 DOM 요소로 바인딩하도록 되어 있기 때문이다.
그러면, this의 값을 의도적으로 변경하려면 어떻게 해야할까?
방법은 2가지가 있다.
1. 화살표 함수 사용
화살표 함수에서의 this는 부모 컨텍스트의 this를 참조하기 때문에 해당 콜백 함수를 호출하기 이전의 this를 출력한다.
let obj = {
name: "obj",
arrow_cb: () => {
console.log(this.name); // undefined
},
func: function() {
console.log(this.name); // obj
}
}
obj.arrow_cb();
obj.func();
2. bind() 사용
bind()를 이용해 this를 특정 값으로 변경하여 출력한다.
let obj = {
name: "obj",
arrow_cb: () => {
console.log(this.name); // 전역객체.name 상
},
func: function() {
console.log(this.name); // obj
}
}
obj.arrow_cb();
obj.func();
obj.func2 = obj.func.bind({name: "bind"});
obj.func2(); // bind
// obj.func.bind({name: "bind"})(); 와 동일
func2 메서드에 특정 값을 주입시켜 name을 obj -> bind로 변경해줬다.
콜백지옥🔥
콜백함수를 익명 함수로 전달하는 과정을 반복되어 코드의 들여쓰기 수준이 감당하기 힘들정도로 깊어지는 현상
코드의 가독성이 저하되고 유지보수하기 힘들어짐
가장 간단한 해결책은 기명함수로 변환하는 것..!
// 익명함수의 콜백지옥
setTimeout((name) => {
let coffeeList = name;
console.log(coffeeList); // 아메리카노
setTimeout((name) => {
coffeeList += ", " + name;
console.log(coffeeList); // 아메리카노, 에스프레소
setTimeout((name) => {
coffeeList += ", " + name;
console.log(coffeeList); // 아메리카노, 에스프레소, 카페라테
}, 3000, "카페라테");
}, 3000, "에스프레소");
}, 3000, "아메리카노");
// 기명함수로 전환
let coffeeList = "";
let americano = function(name) {
coffeeList = name;
console.log(coffeeList);
setTimeout(espresso, 3000, "에스프레소");
}
let espresso = function(name) {
coffeeList += ", " + name;
console.log(coffeeList);
setTimeout(caffelatte, 3000, "카페라테");
}
let caffelatte = function(name) {
coffeeList += ", " + name;
console.log(coffeeList);
}
setTimeout(americano, 3000, "아메리카노");
하지만 주로 콜백함수는 이벤트 처리나 서버 통신과 같이 비동기적 작업을 수행하기 위해 사용되므로
ES6에서는 Promise, Generator 등이 도입되었고, ES2017에서는 async/await가 도입하면서 콜백 지옥을 해결할 수 있다.