[JavaScript] 4. callback
callback 함수
● 콜백함수에 대해서
- 다른 코드의 인자로 넘겨주는 함수 (forEach, setTimeout)
- callback = call(부르다) + back(되돌아오다) = 되돌아와서 호출하는 것
즉, 제어에 대한 권한을 줄테니 입력된 로직으로 처리해라
- 다른 코드(함수 or 메서드)에 인자로 넘겨줌으로 제어권도 함께 위임한 함수
- 자체적으로 내부 로직에 적절한 시점에 실행
● 제어권
1. 호출 시점
let count = 0;
// timer : 콜백 내부에서 사용할 수 있는 '어떤 게 돌고있는지' 알려주는 id값
let timer = setInterval(function() {
console.log(count);
if(++count > 4) clearInterval(timer);
}, 300);
let count = 0;
let cbFunc = function () {
console.log(count);
if (++count > 4) clearInterval(timer);
};
let timer = setInterval(cbFunc, 300);
// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)
- 원래 cbFunc() 수행하면 호출주체와 제어권은 모두 사용자
- setInterval에 넘겨주면 호출주체와 제어권은 모두 setInterval
| code | 호출 주체 | 제어권 |
| cbFunc(); | 사용자 | 사용자 |
| setInterval(cbFunc(), 300); | setInterval | setInterval |
2. this
- 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우.
→ 그 대상을 참조
// 예시 Array.prototype.map
Array.prototype.mapaaa = function (callback, thisArg) {
let mappedArr = [];
for (let i = 0; i < this.length; i++) {
// call의 첫 번째 인자는 thisArg가 존재하는 경우는 그 객체, 없으면 전역객체
// call의 두 번째 인자는 this가 배열일 것(호출의 주체가 배열)이므로,
// i번째 요소를 넣어서 인자로 전달
let mappedValue = callback.call(thisArg || global, this[i]);
mappedArr[i] = mappedValue;
}
return mappedArr;
};
const a = [1, 2, 3].mapaaa((item) => {
return item * 2;
});
console.log(a);
- 제어권을 남겨받을 코드에서 call/apply 메서드의 첫 번 째 인자에서
콜백 함수 내부에서 사용될 this를 명시적으로 binding 하기 때문에
→ this에 다른 값이 담길 수 있는 것.
● 콜백 함수는 함수
- 어떤 객체의 메서드를 전달하더라도, 그 메서드는 메서드가 아닌 함수로 호출
● 콜백 함수 내부의 this에 다른 값 바인딩(binding)
- 가장 좋은 방법 : bind 메서드 활용
let obj1 = {
name: 'obj1',
func: function () {
console.log(this.name);
}
};
//함수 자체를 obj1에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj1로 고정
setTimeout(obj1.func.bind(obj1), 1000);
var obj2 = { name: 'obj2' };
//함수 자체를 obj2에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj2로 고정
setTimeout(obj1.func.bind(obj2), 1500);
● 콜백 지옥 ★ ★
- 콜백 함수를 익명 함수로 전달하는 과정이 반복되어
코드의 들여쓰기가 보기에 매우 나쁜 경우 (어지럽고 혼돈스러워서 지옥)
- 왜 일어날까? = 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생
- 뭐가 문젠데? = 가독성이 나쁘고 유지보수가 힘듦
- 동기(synchronous)
1) 현재 실행중인 코드 끝나면 다음 코드 실행
2) CPU 계산에 의해 즉시 처리가 가능한 대부분의 코드 = 동기적 코드
3) CPU가 계산하는 데에 오래 걸리는 코드 = 동기적 코드
- 비동기(asynchronous => async)
1) 실행 중인 코드의 완료 여부 무관하게 즉시 다음 코드로.
2) setTimeout, addEventListner 등
3) 별도의 요청, 실행 대기, 보류 등과 관련된 코드 모두 비동기적 코드

→ 동기, 비동기를 매우 적절하게 설명한 이미지
- 콜백 지옥의 예시 ▼
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,
"에스프레소"
);
- 해결 방안
→ Promise = 비동기 처리에 대해 처리가 끝나면 알려준다는 '약속(promise)'
- new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 즉시 실행
- 내부의 resolve(or reject) 함수 호출 구문이 있을 경우,
둘 주 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않음
- 비동기 작업이 완료될 때 resolve, reject 호출
- 표현하는 방법
Generator ▼
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();
Promise ~then + Async/await ▼
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();