예를 들어, 어떤 함수 X를 호출하면서, 특정 조건일때 함수 Y를 실행해서
나에게 알려달라는 요청을 보내는것. 되돌아 호출해달라는 명령이다.
다른코드(함수나 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다. 콜백함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행할것이다.
var count = 0;
var cbFunc = function(){
console.log(count);
if(++count > 4) clearInterval(timer);
}
var timer = setInterval(cbFunc, 300);
//실행 결과
//0 1 2 3 4
cbFunc() 함수의 호출주체는 사용자, 제어권도 사용자이다.
하지만 setInterval(cbFunc, 300)의 호출주체는 setInterval,
제어권도 setInterval이 가지게된다.
제어권을 넘겨받은 코드는 콜백 함수 호출시점에 대한 제어권을 가지게된다.
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 ]
콜백함수를 호출하는 주체가 사용자가 아닌 map 메서드이므로 map 메서드가 콜백함수를 호출할때 인자에 어떤 값들을 어떤 순서로 넘길 것인지가 전적으로 map 메서드에달려있다.
콜백 함수로 어떤 객체의 메서드를 전달하더라도, 그 메서드는 메서드가
아닌 함수로써 호출이된다.
var obj = {
vals: [1, 2, 3],
logValues: function(v, i){
console.log(this, v, i);
}
};
obj.logValues(1, 2);
[4, 5, 6].forEach(obj.logValues);
//결과
obj, 1 2
window, 4 0
window, 5 1
window, 6 2
obj.logValues를 forEach 함수의 콜백 함수로서 전달하였다.
obj를 this로 하는 메서드를 그대로 전달한것이 아니라, 함수만 전달한것이라 obj와 직접적 연관이 사라지게된다.
객체의 메서드를 콜백 함수로 전달하면 해당객체를 this로 볼수가 없게되는데, 만약 this로 볼수있게 하려면 어떻게 해야할까.
별도의 인자로 this를 받는 함수의 경우는 예외가 되겟지만
다른경우네는 this를 다른변수에 넣어서 this 대신 그변수를 사용하게 한다.
var obj1 = {
name : 'obj1',
func : function(){
var self = this;
return function(){
console.log(self.name);
};
}
};
var callback = obj1.func();
setTimeout(callback, 1000);
var obj1 = {
name : 'obj1',
func: function(){
console.log(obj1.name);
}
};
setTimeout(obj1.func , 1000);
직접 명시를 하게되면 그 객체의 함수를 다른곳에서는
다시 사용할수없다는 단점이있다.
'''
(첫번째 코드 그대로)
var obj2 ={
name : 'obj2',
func : obj1.func
};
var callback2 = obj2.func();
setTimeout(callback2, 1500);
var obj3 = { name : 'obj3'};
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);
call 메소드를 사용하여서 사용할 객체를 명시해주어
함수를 재활용 할수있다.
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), 1000);
bind 메서드를 사용하면 this에 다른값을 바인딩 할수있다.
콜백함수를 익명함수로 전달하는 과정이 반복되어 코드의 들여쓰기
수준이 감당되지 않을정도로 깊어지는 현상이며 주로 이벤트 처리와 서버통신과 같은 비동기 처리 과정에서 나타나게된다
동기적인 코드는 현재 실행중인 코드가 완료도니 후에야 다음 코드를
실행하는 방식
비동기적인 코드는 현재 실행 중인 코드의 완료 여부와 무관하게
즉시 다음 코드로 넘어가는것. 별도의 요청, 실행대기, 보류등과 관련된
코드는 비동기적인 코드이다.
콜백 지옥 예시 ex)
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, '에스프레소');
콜백 지옥 예시를 조금 개선한 코드(익명의 콜백함수를 모두 기명함수로 변경)
var coffeeList = '';
var addEspresso = function(name){
coffeeList = name;
console.log(coffeeList);
setTimeout(addAmericano, 500, '아메리카노');
}
var addAmericano = function(name){
coffeeList += ', ' + name;
console.log(coffeeList);
setTimeout(addMocha, 500, '카페모카');
}
var addMocha = function(name){
coffeeList += ', ' + name;
console.log(coffeeList);
setTimeout(addLatte, 500, '카페라떼');
}
var addLatte = function(name){
coffeeList += ', ' + name;
console.log(coffeeList);
}
setTimeout(addEspresso, 500 , '에스프레소');
하지만 일회성 함수를 전부 변수에 할당하는것이 이해가안된다.
Javascript는 그래서 ES6에서 Promise, Generator를 도입하고
ES2017에서는 async / await가 도입됬다.
new Promise(function (resolve){
setTimeout(function(){
var name = '에스프레소';
console.log(name);
resolve(name);
}, 500);
}).then(function (prevName) {
return new Promise(function (resolve){
setTimeout(function(){
var name = prevName + ', 아메리카노';
console.log(name);
resolve(name);
}, 500);
})
}).then(function (prevName) {
return new Promise(function (resolve){
setTimeout(function(){
var name = prevName + ', 카페모카';
console.log(name);
resolve(name);
}, 500);
})
}).then(function (preName){
return new Promise(function (resolve){
setTimeout(function (){
var name = preName + ', 카페라떼';
console.log(name);
resolve(name);
}, 500);
});
});
//결과값
에스프레소
에스프레소, 아메리카노
에스프레소, 아메리카노, 카페모카
에스프레소, 아메리카노, 카페모카, 카페라떼
var addCoffee = function(name){
return function(prevName){
return new Promise(function (resolve){
setTimeout(function(){
var newName = prevName ? (prevName + ', ' + name) : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
var addCoffee = function(prevName, name){
setTimeout(function(){
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
var coffeeGenerator = function* (){
var espresso = yield addCoffee('', '에스프레소');
console.log(espresso);
var americano = yield addCoffee(espresso, '아메리카노');
console.log(americano);
var mocha = yield addCoffee(americano, '카페모카');
console.log(mocha);
var latte = yield addCoffee(mocha, '카페라떼');
console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
Generator 함수를 실행하면 Iterator가 반환되는데, Iterator는 next라는 메서드를 가지고있다. 이 next메서드를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈추고, 다시 next를 호출하면 앞서 멈췄던 부분에서 다음 yield에서 함수의 실행을 멈추어준다. 비동기작업이 완료되는 시점마다 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(name);
};
await _addCoffee('에스프레소');
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
}
coffeeMaker();
비동기 작업을 수행하고자 하는 함수앞에 async를 표기하고,
함수 내부에서 실질적인 비동기 작업이 일어날때마다 await를 표기하는것만으로 뒤의 내용을 Promise로 자동 전환하고 해당 내용이 resolve된 후에야 다음으로 진행한다.
!!이내용은 코어 자바스크립트(정재남지음) 책을보고 정리한 내용입니다