[코어자바스크립트]콜백 함수 알아보기

콜백 함수

콜백함수란?

다른 코드의 인자로 넘겨주는 함수
콜백함수를 넘겨받은 코드는 콜백 함수를 필요에 따라 적절한 시점에 시행한다.

콜백함수는 제어권과 관련이 깊다.
callback => 되돌아 호출해달라는 명령
어떤 함수 x를 호출하면서 '특정 조건일 때 함수 y를 실행해서 나에게 알려달라'는 요청을 함께 보낸다.
이 요청을 받은 함수 x의 입장에서는 해당 조건이 갖춰졌는지 여부를 스스로 판단하고 y를 직접 호출한다.

이처럼 콜백 함수는 다른 코드에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.

콜백 함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백함수를 적절한 시점에 실행할 것이다.

제어권

var count = 0;
var timer = setInterval(function(){console.log(count); if(++count > 4) clearInterver(timer);}, 300); 
//0.3초 마다 count 출력, 4번 출력 후 끝남
var intervalID = setInterval(func, delay[, param1, param2,...])

callback 함수 func, 시간 delay(ms단위)을 필수 매개변수로 받고 고유 ID를 반환하는 함수.

콜백함수를 인자로 넘겨주면 setInterval함수가 스스로의 판단에 따라 함수를 실행한다.
호출 주체도 setInterval 함수, 제어권도 setInterval 함수가 가지게 된다.

이렇게 콜백함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.

this

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

Array.prototype.map = function(callback, thisArg){
    var mappedArr = [];
    for(var i = 0; i < this.length; i++){

    }
}
setTimeout(function(){console.log(this);}, 300); // (1) window

[1, 2, 3, 4, 5].forEach(function(x) {
    console.log(this);// (2) window * 5
    });

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
    .addEventListener('click', function(e){ console.log(this, e) // (3) <button id="a">클릭</button>, MouseEvent
    });

각각 콜백 함수 내에서의 this를 살펴보면, 우선(1)의 setTimeout에서 콜백 함수를 호출할 때 call 메서드의 첫번째 인자에 자동적으로 전역객체를 넘기기 때문에 콜백 함수 내부에서의 this가 전역 객체를 가리킨다.
(2)의 forEach는 별도의 인자로 this를 받는 경우에 해당하지만 별도의 인자로 this를 넘겨주지 않았기 때문에 전역 객체를 가리키키게 된다.

array.forEach(callbackFunction(currnetValue, index, array), thisArg)

forEach 함수에서 thisArg에 원하는 this값을 넣어 주면 된다.

콜백 함수는 함수다

콜백함수는 함수이기 때문에 콜백함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로서 호출된다.

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

obj.logValues(1, 2); 
// 객체의 메서드로서 호출 {vals: [1, 2,3] , logvalues : f } 1 2

[4, 5, 6].forEach(obj.logValues); 
//currnetValue, index가 들어간다
//Window 4 0
//Window 5 1
//Window 6 2

어떤 함수의 인자에 객체의 메서드를 전달하더라도 이는 결국 메서드가 아닌 함수이다. obj를 this로 하는 메서드를 그대로 전달한 것이 아니라, obj.logValues가 가리키는 함수를 전달했기 때문이다. 따라서 this는 전역 객체를 바라보게 된다.

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

그럼에도 콜백 함수 내부에서 this가 객체를 바라보게 원하고 싶다면, 별도의 인자로 받는 경우에는 거기에 넘겨주면 되지만 그렇지 않은 경우에는 임의로 값을 바꿀 수 없다.
그래서 전통적으로는 this를 다른 변수에 담아 콜백함수로 활용할 함수에서는 this 대신 그 변수를 사용하게 하고, 이를 클로저로 만드는 방식을 썼다.

var obj1 = {
    name : 'obj1',
    func : function(){
        var self = this;
        return function(){ 
            console.log(self.name); 
        }
        // 객체의 this를 self에 할당하고, self의 name을 출력하는 함수를 return 하는 함수
    }
};

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

이렇게 func 메서드 내부에서 self 변수에 this를 담고 익명 함수를 선언과 동시에 반환했다.
이렇게 되면 func 함수가 반환되면서 내부 함수가 반환되어 callback 변수에 담기고, 1초 뒤에 obj1을 출력한다.
하지만 너무 번거롭다.

var obj1 = {
    name : 'obj1',
    func : function(){
        console.log(obj1.name); //obj1.name을 출력
    }
};

setTimeout(obj.func, 1000);

이렇게 쓴다면 obj1.name이 출력되지만 작성한 함수를 재활용하지는 못한다. obj1로 고정되어 있기 때문이다.

bind 메서드 활용

bind 메서드는 ES5에서 등장했다. bind 메서드는 this를 지정한 함수를 반환하기 때문에 간결하게 사용할 수 있다.

var obj1 = {
    name : 'obj1',
    func : function(){
        console.log(this.name);
    }
};
var obj2 = {
    name : 'obj2'
}
setTimeout(obj1.func.bind(obj1), 1000); // obj1
setTimeout(obj1.func.bind(obj2), 1000); // obj2

콜백 지옥과 비동기 제어

###콜백 지옥이란?

콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상으로, 자바스크립트에서 흔히 발생하는 문제이다.

주로 이벤트 처리나 서버 통신과 같이 비동기적인 작업을 수행하기 위해 이러한 형태가 자주 등장하곤 하는데, 가독성이 떨어질 뿐더러 코드를 수정하기도 어렵다.

비동기 - 동기

동기적 코드 - 현재 실행중인 코드가 완료된 후에야 다음코드를 실행하는 방식이다.
비동기적 코드 - 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.

CPU 계산에 의해 즉시 처리 가능한 대부분의 코드는 동기적 코드라고 할 수 있고, 계산하는 데 시간이 많이 필요한 경우라 하더라도 이는 동기적인 코드다.

반면 특정한 시간, 또는 특정 요청, 사용자의 직접적 개입이 있을 때, 웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 응답이 왔을 때 실행해야 하는 코드는 비동기적 코드이다.

그렇다면 이제 콜백 지옥에 한번 빠져보자!

setTimeout(function(name){
    var coffeeList = name;
    console.log(coffeeList);

    setTimeout(function(name){
        coffeeList += ', '+ name;
        console.log(coffeeList);
    
        setTimeout(function(name){
            coffeeList += ', '+ name;
            console.log(coffeeList);

            setTimeout(function(name){
                coffeeList += ', '+ name;
                console.log(coffeeList);
            }, 500, '카페라떼')
        }, 500, '카페모카');
    }, 500, '아메리카노');
}, 500, '에스프레소');

어질어질...
값이 전달되는 순서가 아래에서 위다.

이것을 해결하는 방법은 여러가지가 있다.
1. 기명함수로 전환.
가장 간단하다고 할 수 있다. 하지만 일회성 함수를 전부 변수에 할당하는 것이 비효율 적이고, 코드 명을 일일이 따라다녀야 하므로 헷갈릴 여지가 있다.

  1. Promise
new Promise(function(resolve){
    setTimeout(function() {
        var name = '에스프레소';
        console.log(name);
        resolve(name);
    }, 500);
}).then(function(prevName){
    return new Promise(function(resolve){
        

    }, 500);


})

좋은 웹페이지 즐겨찾기