콜백 지옥과 비동기 제어
콜백지옥
콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기가 감당이 힘들 정도로 깊어지는 현상
const accessToken = "token"; function fetchMyOrders() { checkNetworkStatus().then(status => if (status) loginCheck(accessToken).then(user => { setUser(user); fetchOrdersById(user.userId).then(orders => { setOrders(orders); }); }) ); }
콜백 지옥과 비동기 제어
동기와 비동기
- 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행되는 코드는 동기
- 특정 시간이 경과되면, 사용자의 직접적 개입이 발생하면, 브라우저 외의 대상에 요청하고 응답이 왔을 때 등 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기
콜백지옥 해결
- 기명함수로 변환
const accessToken = "token"; function fetchMyOrders() { checkNetworkStatus().then(handleNetworkStatus); } function handleNetworkStatus(status) { if (status) loginCheck(accessToken).then(handleLogin); } function handleLogin(user) { setUser(user); fetchOrdersById(user.userId).then(handleOrder); } function handleOrder(orders) { setOrders(orders); }
콜백지옥 해결
비동기 작업의 동기적 표현(1) - Promise(1)
new Promise(function (resolve) { checkNetworkStatus().then(status => { resolve(status) }); }).then(function (status) { return new Promise(function (resolve) { if (status) loginCheck(accessToken).then(user => { setUser(user); resolve(user); }) }); }).then(function (user) { return new Promise(function (resolve) { fetchOrdersById(user.userId).then(orders => { setOrders(orders); resolve(orders); }) }); })
ES6 Promise는 new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 바로 실행되고, resolve
또는 reject
가 호출되면 Promise의 then
또는 catch
구문으로 넘어감 ⇒ 비동기 작업의 동기적 표현 가능
비동기 작업의 동기적 표현(2) - Promise(2)
var networkRequest = function (reqFunc, handleResult) { return function (prevResult) { return new Promise(function(resolve){ reqFunc(prevResult).then((res) => resolve(handleResult ? handleResult(res) : res)) }) } } networkRequest(checkNetworkStatus)() .then(networkRequest(loginCheck, (user) => { setUser(user); return user; })) .then(networkRequest(fetchOrdersById, (orders) => { setOrders(orders); return orders; }))
비동기 작업의 동기적 표현(3) - Generator
var request = function(params, reqFunc) { reqFunc(params).then((res) => requests.next(res)); } var requestGenerator = function* () { var status = yield request(null, checkNetworkStatus); var user = yield request(accessToken, loginCheck); setUser(user); var orders = yield request(user.userId, fetchOrdersById); setOrders(orders); } var requests = requestGenerator(); requests.next();
ES6의 Generator를 이용. *
이 붙은 함수가 Generator 함수로, 제너레이터 함수를 호출하면 next
메소드를 갖고 있는 Iterator 를반환. next
메소드를 호출하면 가장 먼저 등장하는 yield 에서 실행을 멈추고 다시 next
가 호출되면 다시 시작, 다음 yield 에서 멈춤. 이러한 방식으로 Generator 내부 코드가 위→아래로 순차적으로 실행
- 비동기 작업의 동기적 표현 (4) - Promise + async/await (편안..)
var requestBuilder = async function () {
var status = await checkNetworkStatus();
var user = await loginCheck(accessToken);
setUser(user);
var orders = await fetchOrdersById(user.userId);
setOrders(orders);
}
ES2017부터 async/await 을 이용, 비동기 작업을 수행하고자 하는 함수 앞에 async
키워드를 붙이고, 실질적 비동기 작업을 요하는 곳에 await
을 붙이면 뒤의 내용을 Promise로 자동 전환 및 resolve 된 후에 다음으로 진행되게 함.