콜백 함수란?
- 다른 코드의 인자로 넘겨주는 함수
call
부르다,호출하다 +back
뒤돌아오다, 되돌다 의 합성어- → 되돌아 호출해달라.
- 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그
제어권
도 함께 위임
제어권
호출시점
var intervalId = scope.setInterval(func,delay[, param1,param2,...]);
- setInterval 함수는
func
와delay
를 필수 인자로 받으며param
은 선택적으로 func에서 사용할 매개변수를 전달
var count = 0;
var cbFunc = function () {
console. log(count);
if (++count > 4) clearInterval (timer);
}
var timer = setInterval(cbFunc,300);
- 다음 함수는 cbFunc이라는 함수를 setInterval이라는
다른 코드
(함수)의 인자로 넘겨준다 → 콜백함수 - 이로 인해서 cbFunc를 실행하는 제어권을
setInterval
이 가지게 된다
code | 호출 주체 | 제어권 |
cbFunc(); | 사용자 | 사용자 |
setInterval(cbFunc,300); | setInterval | setInterval |
콜백함수의 인자
Array.prototype.map(callback[,thisArg])
callback: function(currentValue,index,array)
- map 매서드는
첫 번째 인자
로callback
함수를 받고, 생략 가능한두 번째 인자
로 콜백함수 내부에서this로 인식할 대상
을 특정할 수 있다. - 두 번째 인자가 생략되면 전역객체가 바인딩.
var newArr2 = [10,20,30].map(function (index, currentValue) {
console.log(index, currentValue);
return currentValue + 5;
});
console.log(newArr2);
//실행결과
// 10 0
// 20 1
// 30 2
// [5, 6, 7]
- map함수를 사용할 때 index와 currentValue의 위치를 바꿔서 사용하면 역할이 바뀌어 버린다,
- function (currentValue, index)라고 사용하면 currentValue가 0,1,2 index가 10,20,30이 되어버린다.
- 즉, 콜백 함수의 제어권을 넘겨받은 코드(map)는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.
this
- 별도의 this를 지정하는 방식
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;
};
- 코드를 보았을때 thisArg 입력되지 않으면 전역객체(window)를 아니면 입력된 thisArg의 값을 지정
- 메서드를 콜백 함수로 전달한 경우에는 단순히 “함수”로서만 호출된다.
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
- obj의 메서드를 콜백함수로 넘겨도 obj자체가 가는것이 아닌 함수로서만 전달되므로 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);
→ 실제로는 이렇게 사용하지 않음.
- 콜백함수 내부에서 this를 사용하지 않는 경우
var obj1 = {
name: 'obj1',
func: function() {
console.log(obj1.name);
},
};
setTimeout(obj1.func, 1000);
→ 위보단 훨씬 좋지만, 작성한 함수를 this를 이용해 재활용할 수 없게 되어버림
- bind()함수 사용하기
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), 1500);
콜백 지옥 벗어나기
- 동기 : 현재 실행중인 코드가 완료된 후에야 다음코드를 실행하는 방식
- CPU에 의해 즉시 처리가 가능한 코드 (연산 등의 시간이 많이 걸린다 하더라도)
- 비동기 : 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어감
- 별도의 요청, 실행 대기, 보류 등과 관련된 코드
예시코드
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,
'에스프레소'
);
- 0.5초 주기마다 커피 목록을 수집하고 출력하는 코드
- 너무 많은 콜백으로 인해서 들여쓰기가 많아 읽기가 힘들어졌다.
콜백지옥 해결하기
기명 함수 사용하기
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, '에스프레소');
- 코드의 가독성도 높이고, 순서도 위에서 아래로 내려간다.
- 하지만 일회성 함수를 전부 변수에 할당하는 것은 비효율적인 일이다.
(ES6) Promise를 이용하기
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에 인자로 넘겨주는 콜백 함수는 바로 실행되지만, 그 내부에 resolve, reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then 혹은 catch 구문으로 넘어가지 않는다.
(ES6) 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();
- Generator 함수는 next 메서드를 생성해서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
- 그리고 다시 next 메서드를 호출하면 멈췄던 부분에서 시작해서 다음에 등장하는 yield에서 함수실행을 멈춘다.
(ES7) Promise + 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();
- 비동기 작업을 수행하는 함수를 async의 키워드를 사용해서 생성하고, 비동기 작업이 필요한 위치에만 await를 표기하는 것만으로 비동기 작업 처리
기타
- async/await 지옥이라는 것도 존재