Promise
를 사용하면 비동기 흐름 제어와, 함수 제어권을 모두 갖기 때문에 Promise
를 사용하는 것이다.다른 라이브러리를 이용해 비동기 처리를 한다면 내가 개발한 프로그램임에도 불구하고 실행 흐름은 라이브러리에 의존하게 되는 제어의 역전
이 발생한다.
이 경우 해당 유틸리티가 내가 전달한 콜백 함수를 제대로 호출한다는 것을 보장할 수 없기 때문에 예기치 못한 상황을 방지하기 위해 방어적인 코드(함수 인자 체크)를 추가하게 된다.
분할 콜백
기능을 제공하는 서드 파티 유틸리티도 있다.function success(arg){
//
}
function failure(arg){
//
}
request('<https://google.com>', success, failure);
에러 우선 스타일
이라는 콜백 패턴을 사용하기도 한다.function response(error, arg) {
if(error){
// 에러처리
} else{
//
}
}
request('<https://google.com>', response);
function foo(){
console.log(num);
}
let num = 1;
bar(foo, 0);
num++;
// bar가 콜백함수를 동기적으로 처리한다면 1이 출력될 것이고
// 비동기적으로 처리한다면 2가 출력될 것이다.
function asyncify(fn){
let originFn = fn,
// 무조건 EventLoop를 거쳐 오도록 설정(비동기로 처리되도록 설정);
id = setTimeout(function(){
// 'setTimeout 콜백'이 '반환된 함수'보다 먼저 호출된 경우
// 바로 기존 콜백(originFn)을 호출하도록 설정
id=null;
// 'setTimeout 콜백'이 '반환된 함수'보다 나중에 호출된 경우
if(fn) fn();
}, 0);
fn = null;
return function(){
if(id){
// '반환된 함수' => 'setTimeout 콜백' 순으로 호출된 경우
fn = originFn.bind(this, ...[].slice.call(arguments));
} else {
// 'setTimeout 콜백' => '반환된 함수' 순으로 호출되 ㄴ경우
originFn.apply(this. arguments);
}
}
}
function foo(a, b, c){
console.log(a, b, c);
console.log(num);
}
function bar(fn){
fn(1, 2, 3);
}
let num = 1;
bar(asyncify(foo), 0);
num++;
동기 코드에서 콜백을 인자로 사용하는 것의 의미는 제어권을 나눠가지는 것을 의미한다.
제어권을 나눠가지게 되면 재사용성과 다형성이 생긴다.
비동기 코드에서는 콜백을 사용하면 순서를 보장하기 어렵기 때문에 콜백에 반환값이 없다. 하지만 동기 코드에서는 순서가 보장되기 때문에 반환값을 가질 수 있고 그 결과 제어권 또한 나눠 가지게 된다.
재사용성
const add = arr => {
const newArr = [];
for(const item of arr){
newArr.push(item + 1);
}
return newArr;
}
const arr = [1, 2, 3];
const result = add(arr);
console.log(result); // [2, 3, 4];
const add = (arr, callback) => {
const newArr = [];
for(const item of arr){
newArr.push(callback(item));
}
return newArr;
}
const arr = [1, 2, 3];
const added1 = add(arr, (item) => item + 1);
const added10 = add(arr, (item) => item + 10);
console.log(added1); // [2, 3, 4];
console.log(added10); // [11, 12, 13];
재활용성
이 생겼다다형성
add
함수는 우리가 자주 사용하던 map
함수와 같은 역할을 한다.const map = (arr, callback) => {
const newArr = [];
for(const item of arr){
newArra.push(callback(item));
}
return newArr;
}
const arr = [1,2,3];
const result = map(arr, (item) => item + 1);
// [2,3,4]
const product = [
{ price : 1000, count : 2, total : 0 },
{ price : 2000, count : 3, total : 0 },
];
const changedProduct = map(product, ({ price, count }) => {
return {
price,
count,
total : price * count
}
});
// [
// { price : 1000, count : 2, total : 2000 },
// { price : 2000, count : 3, total : 6000 },
// ]
콜백 패턴을 사용한다고 해서 무조건 좋다 나쁘다는 것이 아니다.
비동기 코드에서는 콜백 패턴을 사용했을 때 함수의 실행이 끝나는 순서에 대한 보장이 없기 때문에 제어 역전에 따른 믿음성 문제가 생기는 것이다.
동기 코드에서 콜백 패턴을 사용하면 재사용성과 다형성이 생기는 이점 또한 있다.
따라서 상황에 맞게 콜백 패턴을 사용해야 한다.
어떤 A라는 함수에 콜백을 넘기면 콜백에 대한 제어권은 **호출부(여기선 메인 프로그램)**을 떠나 A 함수에게 넘어가게 된다.