콜백 함수 callback function : 다른 코드의 인자로 넘겨주면서 제어권도 함께 주는 함수
call (부르다, 호출하다) + back (되돌아오다, 되돌다) = 되돌아 호출해달라
콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행
A와 B는 아침 6시에 깨어나야 한다. 제어권
A는 불안한 마음에 수시로 깨어 시계를 확인한다.
A는 수시로 시간을 구하는 함수를 직접 호출
B는 알람시계 6시로 설정하고 잠에 든다.
B는 시계의 알람을 설정하는 함수 호출 6시에알람을 울리는 결과 반환
첫 번째 매개변수 setInterval
두 번째 매개변수 300
var count = 0;
var timer = setInterval(function () {
console.log(count);
if (++count > 4) clearInterval(timer);
}, 300);
var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);
scope : Window, Worker 객체 모두 setInterval 메서드 제공func : 함수 delay : 밀리초 msparam1, param2, ...func에 넘겨준 함수는 매 delay(ms)마다 실행
setInterval 실행하면 반복적으로 실행되는 내용 자체를 특정할 수 있는 고유한 ID 값 반환
변수에 담는 이유 : 반복 실행되는 중간에 종료(clearInterval)할 수 있게 하기 위해서
var count = 0;
var cbFunc = function () {
console.log(count);
if (++count > 4) clearInterval(timer);
}, 300);
var timer = setInterval(cbFunc, 300);
// 실행 결과
// 0 (0.3초)
// 1 (0.6초)
// 2 (0.9초)
// 3 (1.2초)
// 4 (1.5초)
timer 변수에는 setInterval의 ID 값 담김
setInterval에 전달한 첫 번째 인자인 cbFunc 함수(콜백 함수)는 0.3초마다 자동으로 실행됨
콜백 함수 내부에서는 count 값 출력, count를 1만큼 증가, 그 값이 4보다 크면 반복 실행 종료
| code | 호출 주체 | 제어권 |
|---|---|---|
| cbFunc(); | 사용자 | 사용자 |
| setInterval(cbFunc, 300); | setInterval | setIntervlal |
콘솔창에는 0.3초에 한 번씩 숫자가 0부터 1씩 증가하며 출력되다가 4가 출력된 이후 종료됨
setInterval이라고 하는 '다른 코드'에 첫 번째 인자로서 cbFunc 함수를 넘겨주자
제어권을 넘겨받은 setInterval이 스스로 판단에 따라
적절한 시점에 (0.3초마다) 이 익명 함수를 실행
=> 콜백 함수의 제어권을 넘겨 받은 코드는 콜백 함수 호출 시점에 대한 제어권 가짐
var newArr = [10, 20, 30].map(function (currentValue, index) {
console.log(currentValue, index);
return currentValue + 5;
});
console.log(newArr);
// 실행 결과
// currentValue index
// 10 0
// 20 1
// 30 2
// [15, 25, 35]
배열 [10, 20, 30]에 map 메서드 호출
첫 번째 매개변수 : 익명 함수
Array.prototype.map(callback[, thisArg])
callback: function(cureentValue, index, array)
map 메서드는 메서드의 대상이 되는 배열의 모든 요소들을 처음부터 끝까지 하나씩 꺼내어
콜백 함수를 반복 호출하고, 콜백 함수의 실행 결과들을 모아 새로운 배열을 만듦
콜백 함수의 첫 번째 인자 cureentValue : 배열의 요소 중 현재값
콜백 함수의 두 번째 인자 index : 현재값의 인덱스
콜백 함수의 세 번째 인자 array : map 메서드의 대상이 되는 배열 자체
콜백 함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만,
제어권을 넘겨받을 코드에서 콜백 함수에
별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다.
Array.porototype.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 메서드
this에는 thisArg 값이 있을 경우에는 그 값을
없을 경우에는 전역객체를 지정하고
첫 번째 인자 : 메서드의 this가 배열을 가리킬 것이므로 배열의 i 번째 요소 값,
두 번째 인자 : i 값,
세 번째 인자 : 배열 자체를 지정해 호출
그 결과가 변수 mappedValue에 담겨 mappedArr의 i 번째 인자에 할당됨
this에 다른 값이 담기는 이유?
제어권을 넘겨받을 코드에서 call/apply 메서드의 첫 번째 인자에 콜백 함수 내부에서의 this가 될 대상을 명시적으로 바인딩하기 때문
setTimeout(function () { console.log(this); }, 300); // Window { ... }
[1, 2, 3, 4, 5].forEach(function (x) {
console.log(this); // Window { ... }
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) {
console.log(this, e); // <button id="a">클릭</button>
} // MouseEvent { isTrusted: true, ... }
);
addEventListener 내부에서 콜백 함수를 호출할 때 call 메서드의 첫 번째 인자에 addEventListener 메서드 this를 그대로 넘기도록 정의되어 있기 때문에
콜백 함수 내부에서의 this가 addEventListener를 호출한 주체인 HTML 엘리먼트를 가리키게 됩니다.
콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로서 호출됨
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 2
this 우회적으로 활용함으로써 다양한 상황에서 원하는 객체 바라보는 콜백 함수
실제로 this를 사용하지도 않고 번거로움
var obj1 = {
name: 'obj1',
func: function () {
var self = this;
return function () {
console.log(self.name);
};
}
};
var callback = obj1.func();
setTimeout(callback, 1000);
obj1.func 메서드 내부에서 self 변수에 this를 담고,
익명 함수를 선언과 동시에 반환
obj1.func 호출하면 앞서 선언한 내부함수가 반환되어 callback 변수에 담김
callback을 setTimeout 함수에 인자로 전달하면
1초(1000ms) 뒤 callback 실행되면서 'obj1'을 출력
간결하고 직관적이지만 this를 이용해 다양한 상황에 재활용할 수 없음
처음부터 바라볼 객체를 명시적으로 obj1로 지정했기 때문에 다른 객체를 바라보게 할 수 없음
-> 전통적인 방식 통용됨
var obj1 = {
name: 'obj1',
func: function () {
console.log(obj1.name);
}
};
setTimeout(obj1.func, 1000);
다양한 객체에 재활용할 필요성이 없는 경우 사용
...
var obj2 = {
name: 'obj2',
func: obj1.func
};
// (obj1의 func를 복사한) obj2의 func를 실행한 결과를 담아 콜백
var callback2 = obj2.func();
setTimeout(callback2, 1500);
// obj1의 func를 실행하면서 this를 obj3가 되도록 지정해 콜백
var obj3 = { name: 'obj3' };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);
var obj1 = {
name: 'obj1',
func: function () {
console.log(this.name);
}
};
setTimeout(obj1.func.bind(obj1), 1000); // obj1
var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500); // obj2
콜백 지옥 callback hell
비동기 asynchronous
setTimeoutaddEventListenerXMLHttpRequest동기 synchronous
0.5초 주기마다 커피 목록 수집, 출력
목적 달성에는 지장 없으나 과도하게 깊어짐
전달되는 순서 아래 -> 위
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(coffeList);
setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function (name) {
coffeeList = name;
console.log(coffeList);
setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function (name) {
coffeeList = name;
console.log(coffeList);
setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function (name) {
coffeeList = name;
console.log(coffeList);
};
setTimeout(addEspresso, 500, '에스프레소');
ES6 new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수
호출할 때 바로 실행되지만 그 내부에 resolve 또는 reject 함수를 호출하는 구문이 있을 경우
둘 중 하나가 실행되기 전까지는 다음 then 또는 오류 구문 catch으로 넘어가지 않음
=> 비동기 작업이 완료될 때 비로소 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현 가능
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);
});
});
함수화해서 더 짧게 표현
var addCoffee = function (name) {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(funtion () {
var newName = prevName ? (prevName + ', ' + name) : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
ES6 Generator 함수 : *가 붙은 함수
Generator 함수 실행하면 Iterator가 반환되는데, Iterator는 next라 메서드를 가지고 있음
이 next 메서드를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춤
이후 다시 next 메서드를 호출하면 앞서 멈췄던 부분부터 시작해서 그다음에 등장하는 yield에서 함수의 실행을 멈춤
비동기 작업이 완료되는 시점마다 next 메서드를 호출해준다면 Generator 함수 내부의 소스가 위 -> 아래 순차적으로 진행됨
var addCoffee = function (prevName, name) {
setTimeout(funtion () {
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();
함수화해서 더 짧게 표현
var addCoffee = function (name) {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(funtion () {
var newName = prevName ? (prevName + ', ' + name) : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
ES2017 async / await
가독성 뛰어나며, 작성법 간단함
비동기 작업을 수행하고자 하는 함수 앞에 async를 표기
함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 표기하는 것만으로
뒤의 내용을 Promise로 자동 전환하고, 해당 내용이 resolve된 이후에야 다음으로 진행함
=> Promise의 then과 흡사한 효과
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);
};
coffeMaker();