Notice
Recent Posts
Recent Comments
Link
뮁이의 개발새발
[코어자바스크립트] Chapter 4. 콜백함수 본문
Chapter 4. 콜백함수
콜백함수란?
- 다른 코드의 인자로 넘겨주는 함수
- ex) 알람을 설정하는 함수(시계의 제어권 X) / 수시로 시간을 확인하는 함수 (시계의 제어권 O)
- 즉 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수
제어권
var count = 0;
var cbFunc = function() {
console.log(count);
if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
// -- 실행 결과 --
// 0 (0.3초)
// 1 (0.6초)
// 2 (0.9초)
// 3 (1.2초)
// 4 (1.5초)
여기서 setInterval의 구조는
var intervalID = scope.setInterval(func, delay[, param1, param2,…]);
- scope에는 Window 객체 또는 Worker의 인스턴스가 들어올 수 있음
- 일반적인 브라우저에서는 window를 생략해서 함수처럼 활용이 가능 (위 예제코드처럼)
- func에 넘겨준 함수는 매 delay(ms) 마다 실행됨.
- 리턴값 X
위 코드에서의 제어권
- 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 메서드
- Array.prototype.map(callback[, thisArg])
- callback : function(current, index, array)
- thisArg는 생략이 가능. 생략할 경우 전역객체가 바인딩됨
- 메서드 대상이 되는 배열의 모든 요소들을 처음부터 끝까지 하나씩 꺼내어 콜백함수를 반복 호출하고 실행 결과들을 모아 새로운 배열을 만듬
- 위 코드에서는 10,20,30의 결과를 함수 내 인자로 넣음으로써 15,25,35라는 새로운 배열을 newArr에 형성해서 넘김
- 함수를 호출할때 넘기는 인자의 위치는 함수가 원하는대로 넣어줘야함.
- 콜백함수의 제어권을 넘겨받은 코드는 콜백함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길것인지에 대한 제어권을 가짐.
this
- 콜백 함수도 함수이기 때문에 기본적으로는 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;
};
- this로 지정하고픈 인자가 있는 경우엔 해당 값을 지정, 없으면 window(전역객체)로 지정
- 제어권을 넘겨받을 코드에서 call/apply 메서드의 첫번째 인자에 콜백 함수 내부에서의 this가 될 대상을 명시적으로 바인딩함
setTimeout(function() {
console.log(this);
}, 300); // (1) Window { ... }
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this); // (2) Window { ... }
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener(
'click',
function(e) {
console.log(this, e); // (3) <button id="a">클릭</button>
} // MouseEvent { isTrusted: true, ... }
);
- (1)은 첫번째 인자에 전역객체를 넘기기 때문에 this가 전역객체
- (2)도 this를 넘겨주지 않음
- (3)은 this로 addEventListener의 this를 넘기도록 되어있어서, 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
- logValues는 메서드로 정의됨.
- this는 obj
- 뒤에는 해당 함수를 함수로써 활용
- 어떤 함수의 인자에 객체의 메서드를 전달하더라도, 이는 결국 메서드가 아닌 함수
콜백 함수 내부의 this에 다른 값 바인딩하기
- 객체의 메서드를 콜백 함수로 전달하면 해당 객체를 this로 바라볼 수 없게 됨
- 콜백 함수 내부에서 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);
- self 변수에 this를 담아 사용
- 번거로움
- 콜백 함수 내부에서 this를 사용하지 않은 경우
var obj1 = {
name: 'obj1',
func: function() {
console.log(obj1.name);
},
};
setTimeout(obj1.func, 1000);
- 간결하지만 this를 다양한 상황에 재활용 할 수 없게 됨.
- 불편하며 메모리를 낭비
- func 함수 재활용
var obj1 = {
name: 'obj1',
func: function() {
console.log(obj1.name);
},
};
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);
- this를 우회적으로나마 다양한 상황에서 원하는 객체를 바라보는 콜백함수를 만들 수 있는 방법
- ES5에서 등장한 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);
콜백 지옥과 비동기 제어
콜백 지옥
콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상
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, '에스프레소');
가독성을 높였으나, 일회성 함수를 전부 변수에 할당해야함.
동기적인 코드
- 현재 실행중인 코드가 완료된 후에 다음 코드를 실행하는 방식
- 대부분 즉시 처리가 가능한 코드
비동기적인 코드
- 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
- 별도의 요청, 실행 대기, 보류 등과 관련된 코드
⇒ 자바스크립트에서는 비동기적인 코드를 동기적으로 보이게끔 처리해주고자 하였음
비동기적 작업의 동기적 표현
Promise (ES6)
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); }); });
- 콜백함수는 호출할때 바로 실행되지만 내부에 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
- Itegrator를 반환함.
- next라는 메서드를 가지고 있음
- 이는 가장 먼저 등장하는 yield의 실행을 멈춤
- 비동기 작업이 완료되는 시점마다 next 메서드를 호출해준다면 Generator 함수 내부의 소스가 위에서서부터 순차적으로 진행됨
Promise + Async/Await (ES2017)
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 된 이후에 다음으로 진행함
정리
- 콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수
- 콜백 함수를 호출하는 시점을 스스로 판단해서 실행
- 인자로 넘겨줄 순서는 따라야함
- this를 바꾸고 싶을 경우 bind 메서드를 활용하면 됨
- 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행됨
- 최근 ECMAScript에서는 Promise, Generator, async/await 등 콜백 지옥에서 벗어날 수 있는 다양한 방법이 나오고 있음
'Front-end > Javascript' 카테고리의 다른 글
[코어자바스크립트] Chapter 5. 클로저 (0) | 2024.05.18 |
---|---|
[코어자바스크립트] Chapter 3. this (1) | 2024.04.10 |
[코어 자바스크립트] Chapter 2. 실행 컨텍스트 (0) | 2024.04.08 |
[코어 자바스크립트] Chapter 1. 데이터 타입 (1) | 2024.04.08 |
[Javascript] jQuery Event (0) | 2021.09.06 |
Comments