1. 콜백함수
- 어떤 함수 X를 호출하면서 "특정조건" 일 때, 함수 Y를 실행해서 나에게 알려달라는 요청을 보낸다. 이 요청을 받은 함수 X의 입장에서는 해당 조건이 갖춰줬는지 여부를 스스로 판단하고 Y를 직접 호출한다.
즉, 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다. 콜백함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행한다.
2. 제어권
2-1) 호출시점
- 아래 예시에서, setInterval에 전달한 첫번째 인자인 cbFunc 함수가 콜백함이다. setInterval 함수는 제어권을 넘겨받고, 스스로 판단에따라 0.3초마다 이 익명함수를 실행한다. 즉 setInterval은 콜백 함수 호출 시점에 대한 제어권을 가진다.
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초
2-2) 인자
- map 메서드의 구조: 첫번째 인자로 콜백함수를 받고, 생략가능한 두 번째 인자로 콜백함수 내부에서 this 로 인식할 대상 지정(생략시에는 전역객체 바인딩)
- 콜백함수의 첫번째 인자에는 배열 요소중 현재값, 두번째 인자에는 현재값의 인덱스, 세번째 인자는 map 메서드의 대상이 되는 배열 자체가 담김
Array.prototype.map(callback[, this Arg])
callback: function(currentValue, index, array)
콜백함수를 호출하는 주체가 사용자가 아닌 map 메서드므로, 인자에 어떤 값들을 어떤 순서로 넘길것인지가 전적으로 map 메서드에 달려있다. 즉, 어떤 순서 로 넘길것인지에 대한 제어권이 있다.
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]
2-3) this
- 콜백함수는 기본적으로 함수라 this는 전액객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.
- addEventListener의 경우, 여기서 this를 소환하면 이것은 바로 e.currentTarget(지금 이벤트가 동작하는 곳)이라는 뜻과 똑같은 의미 = addEventListener 부착된 HTML 요소(클릭 버튼)이다.
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>
})
3. 콜백함수는 함수다
- 콜백함수로 어떤 객체의 메서드를 전달하면, 그 메서드는 메서드가 아니라 함수로서 호출된다.
어떤 함수의 인자에 객체의 메서드를 전달해도, 결국 메서드가 아니라 함수임
var obj = {
vals: [1,2,3],
logValues: function(v,i){
console.log(this,v,i);
}
};
//메서드 호출이므로 obj 객체를 this로 가리킴
obj.logValues(1,2); //{vals: Array(3), logValues: ƒ} 1 2
[4,5,6].forEach(obj.logValues) //obj.logValues가 가리키는 함수만 전달하였으므로 window가 this가 됨
//Window {...} 4 0
//Window {...} 5 1
//Window {...} 6 2
4. 콜백함수 내부의 this에 다른 값 바인딩하기
- 콜백함수 내부에서 this가 객체를 바라보게 하고 싶다면, ES5에 나온 bind 메서드를 쓰는 방법이 있다.
var obj1 = {
name: 'obj1',
func: function(){
console.log(this.name);
}
};
setTimeout(obj1.func.bind(obj1), 1000); // this를 obj1로 바인딩했으므로 obj1 출력
var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500); // this를 obj2로 바인딩했으므로 obj2 출력
5. 콜백지옥과 비동기 제어
- 비동기: 현재 실행중인 코드 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.( = 요청과 응답이 동시에 이루어지지 않는다)
- 동기: 현재 실행중인 코드가 완료된 후에 다음 코드를 실행하는 방식 ( = 요청과 응답이 동시에 이루어짐)
- 비동기방식의 필요성?
화면에서 서버로 데이터를 요청했을때 서버가 언제 그 요청에 대한 응답을 줄지 모르는데 계속 기다릴 수 없고, 텅 빈 화면을 보여줄 수 없으니깐!
▪setTimeout(특정 시간이 경과되기 전까지 어떤 함수 실행을 보류)
▪addEventListener(사용자의 직접적인 개입이 있을 때 비로소 어떤 함수를 실행하도록 대기)
▪XMLHttpRequest(웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 그에 대한 응답이 왔을 때 비로소 어떤 함수를 실행하도록 대기)
별도의 요청, 실행대기, 보류등과 관련된 코드는 비동기적 코드이다.
🌟콜백지옥의 예시
setTimeout(function (name) {
let 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, '에스프레소');
🔸 콜백지옥 해결 - 기명함수로 변환
- 코드의 가독성을 높이고, 함수 선언과 호출을 구분할 수 있다면 위에서 아래로 읽어내려가는데 어려움이 없음
let coffeeList = '';
const addEspresso = function (name) {
coffeeList = name;
console.log(coffeeList); // "에스프레소"
setTimeout(addAmericano, 500, '아메리카노');
};
const addAmericano = function (name) {
coffeeList += ', ' + name;
console.log(coffeeList); // "에스프레소, 아메리카노"
setTimeout(addMocha, 500, '카페모카');
};
const addMocha = function (name) {
coffeeList += ', ' + name;
console.log(coffeeList); // "에스프레소, 아메리카노, 카페모카"
setTimeout(addLatte, 500, '카페라떼');
};
const addLatte = function (name) {
coffeeList += ', ' + name;
console.log(coffeeList); // "에스프레소, 아메리카노, 카페모카, 카페라떼"
};
setTimeout(addEspresso, 500, '에스프레소');
5-1) 비동기작업의 동기적 표현 - promise
- new 연산자와 함께 호출한 promise의 인자로 넘겨주는 콜백함수는 호출할 때 바로 실행, 그 내부에 resolve 또는 reject 함수를 호출하는 구문이 있으면 둘 중 하나가 실행되기 전까지는 다음 then or catch 구문으로 넘어가지 않는다.
- Promise 객체는 3가지 상태를 가진다.
- 대기(pending) : 아직 실행되지 않은 초기 상태
- 이행(fulfilled) : 작업이 성공적으로 완료됨.
- 거부(rejected) : 작업이 실패함.
const addCoffee = function (name) {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
const newName = prevName ? (prevName + ', ' + name) : name;
console.log(newName);
resolve(newName);
}, 500);
});
}
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'))
5-2) 비동기작업의 동기적 표현 - Generator
- ES6에서 등장, *가 붙은 함수
- Generator 함수를 실행하면 Iterator가 반환되며, next라는 메서드를 가지고 있다. next 메서드를 호출하면 Generator 내부에서 제일 처음 등장하는 yield 함수의 실행을 멈추고, next를 한번 더 호출하면 그 다음 등장 yield 함수를 멈춘다
- next()는 항상 아래 두 프로퍼티를 가진 객체를 반환
- value: 산출 값
- done: 함수 코드 실행이 끝났으면 true, 아니라면 false
const addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
const coffeeGenerator = function* () {
const espresso = yield addCoffee('', '에스프레소');
console.log(espresso);
const americano = yield addCoffee(espresso, '아메리카노');
console.log(americano);
const mocha = yield addCoffee(americano, '카페모카');
console.log(mocha);
const latte = yield addCoffee(mocha, '카페라떼');
console.log(latte);
};
const coffeeMaker = coffeeGenerator();
coffeeMaker.next();
5-3) 비동기작업의 동기적 표현 - Promise + Async/await
- 자바스크립트의 비동기 처리 패턴 중 가장 최근에(ES2017) 나온 문법이며, 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완함
- 비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await 을 표기해야 뒤에 내용을 Promise로 자동 전환하고, 해당 내용이 resolve된 이후에 다음으로 진행
async function 함수명() {
await 비동기 처리 메서드명 //HTTP 통신을 하는 비동기 처리 코드 앞, 일반적으로 AXIOS API 호출 코드
}
const addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(name);
}, 500);
});
};
const coffeeMaker = async function () {
let coffeeList = '';
const _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();
'개발공부 > 자바스크립트' 카테고리의 다른 글
[JavaScript] 스터디 7일차_ 클로저 (2) 활용 (0) | 2021.07.01 |
---|---|
[JavaScript] 스터디 6일차_ 클로저 (1) 개념 및 메모리 관리 (0) | 2021.06.28 |
[JavaScript] 스터디 4일차_ this (2) call, apply, bind (0) | 2021.06.20 |
[JavaScript] 스터디 3일차_ this (1) (0) | 2021.06.20 |
[JavaScript] 스터디 2일차_ 실행컨텍스트 (0) | 2021.06.16 |