콜백 함수는 다른 코드의 인자로 넘겨주는 함수이다.
콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행한다.
쉽게 말하자면 알람을 설정하는 행위와 비슷하다고 볼 수 있다.
알람을 맞추지 않으면 본인이 수시로 시계를 확인해야하지만 (시계의 제어권이 본인에게 있음)
알람을 맞추게 되면 시계가 해당 시각이 됐을 때 알람을 울려서 시간 정보를 우리에게 알려준다. (시계의 제어권이 시계에게 있음)
이처럼 콜백 함수는 제어권과 관련이 깊다.
var count = 0;
var cbFunc = function() {
console.log(count);
if( ++count > 3 ) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300); // timer에는 setInterval의 ID값이 담김
// -- 실행 결과 --
// 0 (0.3초)
// 1 (0.6초)
// 2 (0.9초)
// 3 (1.2초) clearInterval를 실행해서 종료
code | 호출 주체 | 제어권 |
---|---|---|
cbFunc(); | 사용자 | 사용자 |
setInterval(cbFunc, 300); | setInterval | setInterval |
setInterval에 첫 번째 인자로서 cbFunc 함수를 넘겨주자 제어권을 넘겨받은 setInterval이 스스로의 판단에 따라 적절한 시점에(0.3초마다) 이 익명함수를 실행했다.
이처럼 콜백 함수의 제어권을 넘겨받은 코드는 함수 호출 시점에 대한 제어권을 가진다.
map() 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환하는 콜백 함수이다.
Array.prototype.map(callback[, thisArg])
callback: function(currentValue, index, array);
map 메서드는 첫 번째 인자로 callback 함수를 받고, 생략 가능한 두번째 인자로 콜백 함수 내부에서 this로 인식할 대상을 특정할 수 있다. thisArg를 생략하면 전역객체가 바인딩 된다.
콜백 함수의 첫 번째 인자에는 배열의 요소 중 현재값이, 두 번째 인자에는 현재값의 인덱스가, 세 번째 인자에는 map 메서드의 대상이 되는 배열 자체가 담긴다.
콜백 함수의 인자는 순서로 인식하기 때문에 첫 번째 인자의 이름을 index로 지정하더라도 인덱스 값이 아닌 순회 중인 배열 중 현재 요소의 값을 배정한다.
콜백 함수를 호출하는 주체가 사용자가 아닌 map 메서드이므로 map 메서드가 인자에 어떤 값들을 어떤 순서로 넘길 것인지는 전적으로 map 메서드에 달려있다.
그러므로 콜백 함수를 사용할 때에는 콜백 함수에 정의된 규칙에 따라 함수를 작성해야 한다.
콜백 함수는 함수이다.
콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로서 호출된다.
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); // Window {...} 4 0
// Window {...} 5 1
// Window {...} 6 1
obj.logValues(1,2)는 obj의 메서드로서 호출했기 때문에 this는 obj를 가리키고,
[4, 5, 6].forEach(obj.logValues)는 obj.logValues가 가리키는 함수를 호출했고 별도로 this를 지정하지 않았기 때문에 this는 전역객체를 가리키게 된다.
왜 이렇게 될까??
map 메소드의 sudo코드를 보자
Array.prototype.map = function (callback, thisArg) {
var mappedArr = [];
for ( var i = 0; i < this.length; i ++){
var mappedValue = callback.call(thisArg || window, this[i], i, this);
mappedArr[i] = mappedValue;
}
return mappedArr;
};
핵심은 call/apply 메서드이다.
Function.prototype.call(thisArg[, arg1[, arg2[,...]]])
call의 첫 번째 인자를 this로 바인딩하는데, map 메서드의 call 메서드 부분에서 callback.call(thisArg || window,..
같이 thisArg 값이 있을 경우에는 그 값을, 없을 경우에는 전역 객체를 지정하기 때문이다.
즉, this에 다른 값이 담기는 이유는 제어권을 넘겨 받을 코드에서 call/apply 메서드의 첫 번째 인자에 콜백 함수 내부에서의 this가 될 대상을 명시적으로 바인딩하기 때문이다.
그러면 콜백 함수 내부의 this에 다른 값을 바인딩 할 수 없을까??
var obj1 = {
name: 'obj1',
func: function () {
var self = this; // obj1가 self에 담김
return function () {
console.log(self.name);
};
}
};
var callback = obj1.func();
setTimeout(callback, 1000); // 결과 obj1
var obj = {
name: 'obj1',
func: function () {
console.log(this.name);
}
};
setTimeout(obj1.func.bind(obj1), 1000); // 결과 obj1
콜백 지옥은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다.
비동기(asynchronous)는 동기(synchronous)의 반대말이다.
동기적인 코드는 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방식이고,
비동기적인 코드는 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식이다.
별도의 요청(XMLHttpRequest), 실행 대기(addEventListener), 보류(setTimeout) 등과 관련된 코드는 비동적인 코드이다.
setTimeout(function (name) {
var coffeeList = name;
console.log(coffeeList);
setTimeout(function (name) {
coffeeList += ', ' + name;
console.log(coffeeList);
...
},500, '카페라떼');
},500, '아메리카노');
// 아메리카노
// 아메리카노, 카페라떼
비동기적인 작업을 동기적으로 보이게끔 처리해주는 장치가 등장했다!
ES6에서는 Promise, Generator등이 도입됐고, ES2017에서는 async/await가 도입됐다.
new Promise(function (resolve) {
setTimeout(function () {
var name = '아메리카노';
console.log(name);
resolve(name); // 콜백 함수는 resolve가 실행되기 전까지는 다음(then)으로 넘어가지 않음
}, 500);
}).then(function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ', 카페라떼';
console.log(name);
resolve(name);
}, 500);
});
});
// 아메리카노
// 아메리카노, 카페라떼
var addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ", " + name : name);
}, 500);
};
var coffeeGenerator = function* () { // *이 붙으면 Generator 함수
var americano = yield addCoffee('', '아메리카노'); // next()를 호출하면 yield에서 함수의 실행이 멈춤
console.log(americano);
var latte = yield addCoffee(americano, '카페라떼');
console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(name);
},500);
});
};
var coffeeMaker = async function() {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ',' : '') + await addCoffee(namae);
};
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();
setTimeout은 Promise반환을 하지 않기 때문에 async와 await를 적용해도 동기적으로 작동되지 않는다고 한다.
따라서 Promise를 직접 반환하게 작성 후 async와 await를 적용해야한다.