- 다른 코드의 인자로 넘겨주는 함수 혹은 회신되는 함수임
- 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
setInterval(callback, milliseconds)
콜백 함수 예제 setInterval )
- setInterval은 ms단위의 주기 단위로 콜백 함수를 실행함
콜백 함수 예제 setInterval )
var count = 0;
var timer = setInterval(function () {
console.log(count);
if(++count > 4) clearInterval(timer);
}, 300)
// 0
// 1
// 2
// 3
// 4
setInterval 구조)
var intervalID = scope.setInterval(func,delay[, param1, param2, ...]);
- 매개변수에 func는 함수이고, delay는 밀리초(ms) 단위의 숫자이고, 나머지 (param1, param2, …)는 func함수를 실행할 때 매개변수로 전달할 인자이다.
- func에 넘겨준 함수는 매 delay(ms)마다 실행되며, 그 결과 어떠한 값도 리턴하지 않는다.
- setInterval를 실행하면 반복적으로 실행되는 내용 자체를 특정할 수 있는 고유한 ID 값이 반환된다. 이를 변수에 담는 이유는 반복 실행되는 중간에 종료(clearInterval)할 수 있게 하기 위해서이다.
forEach
forEach(callbackFn, [thisArg])
[callbackFn](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#callbackfn)
- 배열의 각 요소에 대해 실행할 함수이다. 반환값은 사용되지 않는다.
[thisArg](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#thisarg)
callbackFn
을 실행할 때this
값으로 사용할 값임
forEach() 예시)
let arr = [1,2,3,4,5];
let entries = [];
arr.forEach(function(v,i){
entries.push([i, v, this[i]]);
}, [10,20,30,40,50]);
console.log(entries);
결과
[
[ 0, 1, 10 ],
[ 1, 2, 20 ],
[ 2, 3, 30 ],
[ 3, 4, 40 ],
[ 4, 5, 50 ]
]
- 콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로서 호출된다.
메서드를 콜백 함수로 전달하는 경우 )
var obj ={
vals : [1,2,3],
logValues : function(v,i) {
console.log(this, v ,i);
}
}
// 메서드로서 호출
obj.logValues(1,2) // {vals: [1,2,3], logValues: ƒ} 1 2
[4, 5, 6].forEach(obj.logValues);
// Window {...} 4 0
// Window {...} 5 1
// Window {...} 6 2
- 어떤 함수의 인자에 객체의 메서드를 전달하더라도 이는 결국 메서드가 아닌 함수일 뿐이다.
콜백 함수 내부에 this에 다른 값을 바인딩하는 방법 )
var obj1 = {
name:'obj1',
func: function() {
var self = this;
return function() {
console.log(self.name);
};
}
};
var callback = obj1.func();
setTimeout(callback, 1000); // obj1
콜백 함수 내부에서 this를 사용하지 않은 경우 )
var obj1={
name: 'obj1',
func: function(){
console.log(obj1.name);
}
};
setTimeout(obj1.func,1000); //obj1
- this를 사용하지 않았을때가 훨씬 간결하고 직관적이다. 하지만 this를 이용해 다양한 상황에 재활용할 수 없게 되었다.
func 함수 재활용 가능 )
...
var obj2= {
name: 'obj2',
func: obj1.func
};
var callback2 = obj2.func();
setTimeout(callback2,1500); // obj2
var obj3 = {name: 'obj3'};
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000); // obj3
- this를 우회적으로 활용함으로써 다양한 상황에서 원하는 객체를 바라보는 콜백 함수를 만들 수 있는 방법이다.
다른 예시)
let arr = [1,2,3,4,5];
let obj = {
vals: [1,2,3],
log : function(v,i) {
if(this.vals) {
console.log(this.vals, v, i);
} else {
console.log(this, v, i);
}
}
}
// 메서드로 호출
obj.log(1,2)
//콜백함수로 전달
arr.forEach(obj.log);
결과
[1, 2, 3] 1 2
Window {…} 1 0
Window {…} 2 1
Window {…} 3 2
Window {…} 4 3
Window {…} 5 4
- 콜백함수로 값을 전달하면 무조건 함수가 나옴. 그래서 obj로 지정하고 싶으면 bind메서드를 쓰거나, forEach의 두번 째 매개변수로 thisArg에 obj를 넣으면 된다.
arr.forEach(obj.log.bind(obj));
arr.forEach(obj.log, obj)
- 메서드로 호출과 콜백함수로 전달 구분
- 콜백 지옥(callback hell) : 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상
- 비동기 : 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.
- setTimeout, addEventListener, XMLHttpRequest 등
- 별도의 요청, 실행 대기, 보류 등과 관련된 코드
- 동기 : 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방식
- CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드
콜백 지옥 )
setTimeout(function (name) { // ---- (4)
var coffeeList = name;
console.log(coffeeList);
setTimeout(function (name){ // ---- (3)
coffeeList += ',' + name;
console.log(coffeeList);
setTimeout(function (name){ // ---- (2)
coffeeList += ',' + name;
console.log(coffeeList);
setTimeout(function (name){ // ----(1)
coffeeList += ',' + name;
console.log(coffeeList);
},500,'카페라떼');
},500,'카페모카');
},500,'아메리카노');
},500,'에스프레소');
// 에스프레소
// 에스프레소,아메리카노
// 에스프레소,아메리카노,카페모카
// 에스프레소,아메리카노,카페모카,카페라떼
- 0.5초 주기마다 커피 목록을 수집하고 출력한다.
- 들여쓰기 수준이 과도하게 깊어져 값이 전달되는 순서가 ‘아래에서 위로’ 향하고 있어 어색하다.
- 이를 해결하는 방법은 익명의 콜백 함수를 모두 기명함수로 전환하는 것이다.
콜백 지옥 해결 - 기명함수로 변환 )
var coffeeList='';
var addEsspresso = 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(addEsspresso,500,'에스프레소');
// 에스프레소
// 에스프레소,아메리카노
// 에스프레소,아메리카노,카페모카
// 에스프레소,아메리카노,카페모카,카페라떼
- 이 방식은 코드의 가독성을 높여 위에서 부터 아래로 순서대로 읽혀 내려간다.
- 자바스크립트에서 비동기적인 작업을 동기적으로, 혹은 동기적인 것처럼 보이게끔 처리해 주는 장치인 ⇒ ES6에서 Promise, Generator ES2017에서 async/await 도입되었다.
- ES6에서 Promise, Generator 도입
- ES2017에서 async/await 도입
비동기 작업의 동기적 표현 - Promise 1)
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(prevName){
return new Promise(function (resolve){
setTimeout(function(){
var name=prevName + ', 카페라떼';
console.log(name);
resolve(name);
},500);
});
});
- Promise는 new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만 내부에 resolve 또는 reject 함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 다음(then) 또는 오류 구문(catch)으로 넘어가지 않는다.
- 따라서 비동기 작업이 완료될 떄 비로소 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능하다.
비동기 작업의 동기적 표현 - Promise 2)
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('카페라떼'));
- 위는 이전에 반복적인 내용을 함수화해서 더욱 짧게 표현한 것이다.
비동기 작업의 동기적 표현 - Generator)
var addCoffee = function(prevName, name) {
setTimeout(function() {
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
**①** var coffeeGenerator = function* () { // '*'이 붙은 함수 -> Generator 함수
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함수이다.
- Generator 함수를 실행하면 Iterator가 반환되고, Iterator는 next라는 메서드를 가진다. 이 next메서드를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
- 이후 다시 next메서드를 호출하면 앞서 멈췄던 부분부터 시작해서 그 다음에 등장하는 yield에서 함수의 실행을 멈춘다.
비동기 작업의 동기적 표현 - Promise + Async/await )
var coffee = 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된 이후에야 다음으로 진행한다.
- 즉, Promise의 then과 흡사한 효과를 얻는다.
- 콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
- 제어권을 넘겨받은 코드는 다음과 같은 제어권을 가진다.
-
- 콜백 함수를 호출하는 시점을 스스로 판단해서 실행함
-
- 콜백 함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있다. 이 순서를 따르지 않고, 코드를 작성하면 엉뚱한 결과가 나온다.
-
- 콜백 함수의 this가 무엇을 바라보도록 할지가 정해져 있는 경우도 있다. 정하지 않은 경우에는 전역객체를 바로봄. 사용자 임의로 this를 바꾸고 싶을 경우 bind 메서드를 활용하면 된다.
-
- 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행된다.
- 비동기 제어를 위해 콜백 함수를 사용하다 보면 콜백 지옥에 빠질 수 있다. Promise, Generator, async/await 등을 사용해서 비동적인 코드를 동기적인 코드로 변환한다.
정재남, 코어 자바스크립트