콜백 함수: 다른 코드의 인자로 넘겨주는 함수.
콜백 함수를 받는 주체가 있음. (forEach, setTimeout 등)
제어권이 그 주체들에 있음 (그 주체가 적절한 시점에 콜백 함수를 실행함).
스폰지밥과 알람시계의 예)
스폰지밥이 6시에 일어나려고 했을 때:
제어권을 넘겨줄테니 너가 알고 있는 그 로직으로 처리해줘
// 콜백 함수 1
// 1초 후 (console.log) 실행.
setTimeout(function () {
console.log("hello");
}, 1000);
// 콜백 함수 2
const numbers = [1, 2, 3, 4, 5];
// forEach가 알아서 로직을 처리
numbers.forEach(function (number) {
console.log(number);
});
즉, 콜백 함수는 다른 코드(함수 또는 메소드)에 인자와 동시에 제어권도 넘겨주는 함수.
// setInterval :
// 반복해서 매개변수로 받은 콜백함수의 로직을 수행한다.
// 반환값: interval(간격)을 고유하게 식별할 수 있는 ID.
// clearInterval 함수를 호출하여 제거할 수 있음.
var count = 0;
// 콜백 함수
var cbFunc = function () {
console.log();
// Interval을 초기화시켜주는 조건
if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)
// map: 배열의 요소를 순회하며 가공하여 새로운 배열을 반환
// 0번째 매개변수: 요소
// 1번째 매개변수: 요소의 인덱스
const arr = [10, 20, 30];
let newArr = arr.map(function (currentValue, index) {
console.log(currentValue, index);
return currentValue + 5;
});
console.log(newArr);
// map 함수는 첫 번째 인자로 콜백 함수, 두 번째 인자로 this를 받음.
Array.prototype.myMap = function (callback, thisArg) {
// map 함수에서 return할 결과 배열
var mappedArr = [];
// 이 함수의 호출 주체는 [1, 2, 3] 배열이기 때문에 this는 배열.
for (let i = 0; i < this.length; i++) {
// this 바인딩을 위한 call 함수 호출
// mappedValue에 this[i]을 할당
// 콜백 함수 내부에서 this를 명시적으로 바인딩하기 때문에 가능
let mappedValue = callback.call(thisArg || global, this[i]);
mappedArr[i] = mappedValue;
}
return mappedArr;
};
console.log(
[1, 2, 3].myMap(function (number) {
return number * 2;
})
);
// obj
// 2가지 속성
var obj = {
vals: [1, 2, 3],
logValues: function (v, i) {
console.log(">>> test starts");
if (this !== global) console.log(this, v, i);
console.log(">>> test ends");
},
};
// method로서 호출
obj.logValues(1, 2);
console.log();
// obj에 있는 logValues 함수를 global에서 호출
// (위의 logValues의 function (~~~) 부분을 넣은 것과 같음)
[4, 5, 6].forEach(obj.logValues);
출력 결과:
>>> test starts
{ vals: [ 1, 2, 3 ], logValues: [Function: logValues] } 1 2
>>> test ends
>>> test starts
>>> test ends
>>> test starts
>>> test ends
>>> test starts
>>> test ends
콜백 함수를 매개변수로 넣을 때는 함수 자체(함수의 매개변수 없이)만 넣어주어야 한다
bind 함수를 이용하여 this로 바인딩된 새로운 함수를 반환
var obj1 = {
name: "obj1",
func: function () {
console.log(this.name);
},
};
var obj3 = { name: "obj3" };
setTimeout(obj1.func.bind(obj3), 1000);
콜백 지옥: 콜백 함수를 익명 함수(function ())로 전달하는 과정이 여러 번 반복되면 들여쓰기가 많아져 가독성도 떨어지고, 어떤 함수가 어떤 기능을 하는 지 이해하기 어렵고, 수정/유지보수 등이 어려워서 생긴 말.
콜백 지옥은 보통 비동기적인 로직에서 많이 생김.
동기? 비동기?
예시)
카페에서 여러 명이 주문을 하려고 줄을 서있을 때
1. 한 사람 주문을 받고 그 사람의 주문을 완료할 때까지 다른 사람들은 대기하는 프로세스. 비효율적 (동기)
2. 한꺼번에 주문을 받고 주문에 따라 소요시간이 빠른 주문건부터 완료하는 프로세스. 효율적 (비동기)
예) setTimeout.
// 비동기적 코드의 이해
setTimeout(function () {
// 1
console.log("여기가 먼저 실행될까?");
}, 2000);
// 2
console.log("아니면 여기?");
예시)
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초 후
에스프레소
// 0.5초 후
에스프레소, 아메리카노
// 0.5초 후
에스프레소, 아메리카노, 카페모카
// 0.5초 후
에스프레소, 아메리카노, 카페모카, 카페라떼
해결 방법 1)
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, '에스프레소');
setTimeout 환경이 아닌 다른 방법으로 비동기적 작업을 수행할 때 순서가 보장되지 않으므로 통신의 오류가 생길 수 있음. 따라서 비동기적 작업을 동기적으로(순서를 보장하는 것처럼) 보이게끔 표현/구현하는 것이 필요함.
3가지 방법: 1) Promise, 2). Generator, 3) async/await
'처리가 끝나면 알려달라'는 약속.
resolve: 성공
reject: 실패
처리 결과에 따라 then, catch문으로 오류사항들을 추적할 수 있음.
위 콜백 지옥을 Promise 방법으로 구현)
new Promise(function (resolve) {
setTimeout(function () {
var name = "에스프레소";
console.log(name);
// 1
resolve(name);
}, 500);
})
// 2
.then(function (prevName) {
// 3
return new Promise(function (resolve) {
setTimeout(function () {
// 4
var name = (prevName += ", 아메리카노");
console.log(name);
// 1
resolve(name);
}, 500);
});
})
// 2
.then(function (prevName) {
// 3
return new Promise(function (resolve) {
setTimeout(function () {
// 4
var name = (prevName += ", 카페모카");
console.log(name);
// 1
resolve(name);
}, 500);
});
})
// 2
.then(function (prevName) {
// 3
return new Promise(function (resolve) {
setTimeout(function () {
// 4
var name = (prevName += ", 카페라떼");
console.log(name);
resolve(name);
}, 500);
});
});
리팩토링된 코드)
// 1
var addCoffee = (name) => {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
// 2
var newName = prevName ? `${prevName}, ${name}` : name;
console.log(newName);
// 3
resolve(newName);
}, 500);
});
};
};
// 4
addCoffee("에스프레소")()
// 5
.then(addCoffee("아메리카노"))
.then(addCoffee("카페모카"))
.then(addCoffee("카페라떼"));
기본적으로 iterator 객체를 반환함.
// 1
var addCoffee = function (prevName, name) {
setTimeout(function () {
// 6
coffeeMaker.next(prevName ? prevName + ", " + name : name);
}, 500);
};
// 2
var coffeeGenerator = function* () {
// 5
var espresso = yield addCoffee("", "에스프레소");
console.log(espresso);
// 7, 5
var americano = yield addCoffee(espresso, "아메리카노");
console.log(americano);
// 7, 5
var mocha = yield addCoffee(americano, "카페모카");
console.log(mocha);
// 7, 5
var latte = yield addCoffee(mocha, "카페라떼");
console.log(latte);
};
// 3
var coffeeMaker = coffeeGenerator();
// 4
coffeeMaker.next();
async: 비동기
await: 기다리다
// 1
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function(){
resolve(name);
}, 500);
});
};
// 2
var coffeeMaker = async function () {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
};
// 3
await _addCoffee('에스프레소');
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();