heyday2024 님의 블로그
[4주차 JS 문법(2)] 콜백지옥과 비동기 제어 본문
1. 콜백지옥
콜백함수를 익명함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬인 경우!
( 주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생 )
----> 가독성이 정말 지옥(hell)임.
2. 동기 vs 비동기
위에 카페는 진동벨 없이 주문요청이 들어오면 작업을 끝낼 때 까지 손님들이 기다림(시간 오래걸림)
그리고, 뒤에 손님들은 무조건 자신의 바로 앞의 주문이 완료되어야만 주문 요청을 할 수 있음.
-----> 동기
아래에 카페는 손님들에게 주문 요청을 다 받고, 진동벨을 나눠준 후 작업이 끝나는 대로 그 진동벨을 울려서 작업을 완료함. (시간적으로 효율적임)
----> 비동기
- 동기 : synchronous
- 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식을 말해요!
- CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드구요.
- 계산이 복잡해서 CPU가 계산하는 데에 오래 걸리는 코드 역시도 동기적 코드에요 😎
- 비동기 : a + synchronous ⇒ async라고들 흔히 부르죠
- 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
- setTimeout, addEventListner 등
- 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드
----> 웹의 복잡도가 올라갈수록 비동기적 코드의 비중이 늘어남
<콜백 지옥 예시>
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)같은 것들이죠.
비동기 작업의 동기적 표현이 필요합니다.
(2) 비동기 작업의 동기적 표현
비동기 작업은 순서를 보장하지 않음.
--> 그러나 순서대로 처리되어야하는 비동기 작업들도 있을 수 있음
(ex) 위의 예시처럼, 음료를 순서대로 처리하는 것....
===> 그래서 비동기 작업의 동기적 표현이 필요함!!!!
<비동기 작업의 동기적 표현(1) - Promise(1)>
Promise는 비동기 처리에 대해, 처리가 끝나면 알려달라는 ‘약속’이에요.
- new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행돼요.
- 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않아요. (처리 성공적으로 완료 : resolve, 실패: reject)
- 따라서, 비동기작업이 완료될 때 비로소 resolve, reject 호출해요.
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);
});
});
- 앞의 promise가 resolve가 실행(작업 성공적으로 완료)되어야만 then으로 그 다음을 실행함.
- then으로 그 다음, 그 다음, 그 다음의 코드를 순차적으로 실행하게함(비동기적 작업은 동기적으로~~ chaining)
<비동기 작업의 동기적 표현(2) - Promise(2)>
직전 예제의 반복부분(then으로 계속 처리했던 코드 내용/작업들이 같았음)을 함수화 한 코드.(refactoring)
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('카페라떼'));
- addCoffee('에스프레소')는 function(prevName)함수 부분임으로 이 함수를 실행시키기 위해 () 뒤에 붙여줌.
- 앞의 함수(작업)가 실행이완료되야만 then 부분이 실행됨(순차적으로)
- prevName이 있을때는 prevName도 다 출력, 없으면 name만. (삼항 연산자)
<비동기 작업의 동기적 표현(3) - Generator>
genertator는 반복할 수 있는 iterable 객체를 반환함.
- 이터러블 객체(Iterable)
function에 *가 붙은 함수가 제너레이터 함수입니다. 제너레이터 함수는 실행하면, Iterator 객체가 반환(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();
-coffeeGenerator는 genertator 함수로 iterable 객체를 반환함(여기서는 coffeMaker)
- yield를 만나면 일단 멈춤, 그 뒤 함수가 끝날 때까지 기다려주고, 다시 next()메서드를 호출하면 중단했던 부분에서 실행 재개함.
<비동기 작업의 동기적 표현(4) - Promise + Async/await>
ES2017에서 새롭게 추가된 async/await 문을 이용.
비동기 작업을 수행코자 하는 함수 앞에 async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 **await**를 붙여주면 됨
Promise ~ then과 동일한 효과를 얻을 수 있어요
//Promise
//예전에는 then(그러면~)을 썼지만, 이젠 async(비동기) /awiat(기다리다)를 써도 됨.
//coffeeMaker 함수에서 호출할 함수, 'addCoffee' 선언
//Promise 반환
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function(){
resolve(name);
}, 500);
});
};
//화살표 함수하면 그 앞에 async 붙여줌 (ex) asunc () =>{}
//async 붙여준 함수 내부 스코프에서 await을 만나, 해당 메서드가 작업이 끝날 때까지 기다림.
var coffeeMaker = async function () {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
};
//promise를 반환하는 함수인 경우, await을 만나면 무조건 끝날 때까지 기다림.
await _addCoffee('에스프레소');
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();
'프론트엔드 부트캠프' 카테고리의 다른 글
[JS 문법 5주차] DOM, DOM_API (3) | 2024.10.24 |
---|---|
This, Class, Scope (0) | 2024.10.23 |
[4주차 JS 문법(1)] 콜백함수 (제어권, this 바인딩) (1) | 2024.10.21 |
[3주차 과제] 객체 복사, this 이해 (4) | 2024.10.20 |
[JS 기초 문법 3주차(3)] 실행 컨텍스트의 세번째 요소, ThisBindings (0) | 2024.10.20 |