익명함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬 수준인 경우
주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행 할 때 발생
동기(synchronous) : 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식
비동기(a + synchronous ⇒ async) : 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식. (setTimeout, addEventListner 등)
별도의 요청, 실행대기, 보류 등과 관련된 코드는 모두 비동기적 코드이다.
💁♀️[ 콜백지옥의 예시와 해결방안 ]
일단 콜백 함수에 흔히 쓰이는 setTimeout의 원리는 이와 같다.
// setTimeout 함수의 동작원리
setTimeout(function(){
// 기본적으로 1000ms이 지나야 여기 로직이 실행이 된답니다 :)
console.log('hi');
}, 1000);
들여쓰기 수준이 낮고 값전달 순서가 아래에서 위로가는 예시코드는 이렇다.
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,
"에스프레소"
);
가독성도 좋지 못하고, 들여쓰기가 너무 심각하다. 이걸 해결하는 첫번째 방법은
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, '에스프레소');
위의 문제 코드보다 가독성이 좋아졌고 위에서 아래로 흐름이 이어진다. 하지만 이름을 다 붙이는게 힘드니까 근복적인 해결책은 아니다.
==> 비동기적인 작업을 동기적으로(동기적인것처럼 보이는) 처리해주는 장치를 마련해주고 있다. 바로 Promise
, Generator
(ES6), async/await
(ES7)이다.
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);
});
});
이 코드의 반복 부분을 함수화한 코드이다.
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
먼저 이터러블 객체(Iterable)의 뜻을 알아야 한다. 반복될 수 있는, 반복할 수 있는 이라는 뜻이다. 제너레이터 문법에 등장하낟. '*'가 붙은 함수가 제너레이터 함수이고, 제너레이터 함수를 실행하면 이터레이터 객체가 반환(next())된다.
iterator 은 객체는 next 메서드로 순환 할 수 있는 객체이고, next 메서드 호출 시, Generator 함수 내부에서 가장 먼저 등장하는 yield
에서 stop
이후 다시 next 메서드를 호출하면 멈췄던 부분 -> 그 다음의 yield
까지 실행 후 stop
==> 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 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();
Promise + Async/await
async/await
문을 이용. 비동기 작업을 수행코자 하는 함수 앞에 async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await
를 붙여주면 된다.Promise ~ then과 동일한 효과를 얻을 수 있다.
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();