
이 포스팅은 이벤트 루프, await , generator 의 관계에 대해 궁금하여 조사한 포스팅 글입니다.
await 에 대해 공부하던중 아래와 같은 코드를 보았다.
function main() {
console.log("main 함수 시작");
A();
console.log("main 함수 끝")
}
async function A() {
console.log("A 시작");
const korea = await "한국";
console.log(korea);
console.log("A 끝");
}
main();
이벤트 루프에 대해 알고 있고 async , await 패턴에 대해 알고 있다면 충분히 예측가능한 결과가 나온다.
인터프리터는 await를 만나면 해당 함수의 실행이 중지되고 await 이후의 코드는 마이크로 큐에 들어가게 된다.
따라서 위 코드의 콘솔 출력을 살펴본다면 아래와 같다.
main 함수 시작
A 시작
// 여기서 await를 만나 await 이후의 코드는 마이크로 큐에 들어간다.
// 콜스택,마이크로 큐에 같이 코드가 있으므로 콜스택에 남은 코드인 main함수가 마저 실행된다.
main 함수 끝
// main 함수가 끝나며 콜스택이 비워지고 마이크로 큐에 들어갔던 나머지 함수 부분이 실행된다.
한국
A 끝
여기서 든 의문은 await 를 만난다면 await의 이후 코드를 마이크로 큐에 넣는다는 부분에서 생겼다…
정확히는 “await 이후 코드 부분을 남긴다” 였다…
특정 문법과 굉장히 비슷한 느낌을 받았다..
javascript ES6에서 새롭게 추가된 문법으로 새로운 함수 형태인 제너레이터 문법이다.
function 키워드 뒤에 * 를 붙히면 제너레이터 함수가 되며 아래와 같이 사용한다.
function* generator() {
console.log("generator 시작");
yield 1;
console.log("generator 끝");
}
const gen = generator();
console.log("첫 번째 next 호출")
const {value, done} = gen.next();
console.log(value, done);
console.log("두 번째 next 호출")
const {value: value2, done: done2} = gen.next();
console.log(value2, done2);
위 코드의 콘솔출력를 먼저 본다면 아래와 같다.
// next가 호출되며 gernerator안의 코드가 시작된다.
첫 번째 next 호출
generator 시작
// yield 를 만나면 제어권을 넘긴다.
1 false
// 다음 next 함수가 호출되면 남은 코드를 실행한다.
두 번째 next 호출
generator 끝
제너레이터에는 특이한 문법이 있는데… yield 키워드를 선언식으로 넣는다면 next함수로 인자를 넣을수 있다는 것이다.
아래를 보자
function* generatorFunction() {
let value = 0;
while (true) {
value = yield value + 1;
}
}
const gen = generatorFunction();
for (let i = 0; i < 3; i++) {
console.log(gen.next(i).value);
}
next 함수를 호출하며 안에 i 값을 넣고 있다.
next 안 인자를 넣으면 yield으로 선언한 값에 인자값을 할당하게 된다.
첫 인자는 무시 한다 (위에서는 i == 0일때 0을 무시)
아래 코드 실행 순서를 보자
for문 시작 i == 0
next(0)호출
// while 문 첫 진입
// 현재 value는 초기화한 0
yield를 만나서 0 + 1를 value로 반환
next(1)호출
yield 앞 value에 1을 할당하고 while문을 돌아 다시 yield 키워드를 만난다.
// 현재 value는 next 인자로 할당받은 1
yield를 만나 1 + 1 을 value로 반환
next(2)호출
yield 앞 value에 2을 할당하고 while문을 돌아 다시 yield 키워드를 만난다.
// 현재 value는 next 인자로 할당받은 2
yield를 만나 2 + 1 을 value로 반환
for문 종료
제너레이터를 보며 헷갈렸던 문법이기에 순서를 적어보았다…
여튼 제너레이터와 yield의 동작을 알아보았다.
await를 만나는 순간 남은 코드부분을 마이크로 태스크 큐에 넣고 다음코드를 이어서 실행한다.
yield 를 만나는 순간 남은 코드부분을 남기고 다음 코드를 이어서 실행한다.
굉장히 비슷한 동작 방식이다… 의문을 느끼고 어떤 관계가 있는지 궁금해 구글링을 해보니 MDN 문서에 이런 글이 적혀있었다.

참고: async/await의 목적은 프로미스 기반 API를 사용하는 데 필요한 구문을 단순화하기 위한 것입니다. async/await의 동작은 제너레이터와 프로미스를 결합하는 것과 유사합니다.
결국 async/await 문법은 제너레이터와 promise로 만들어진 Syntactic sugar 이라는것…
axios.get('url')
.then(res => console.log(res.data));
get 요청으로 콘솔에 출력하는 코드이다. then 을 이용했이게 then 에 콜백함수가 들어가있다.
위 코드를 generator를 이용한 동기적으로 보이게 바꿔보자
function* axiosGet() {
const response = yield axios.get('url')'
console.log(response);
}
const generator = axiosGet();
// 첫 next를 호출하여 axios.get 의 promise를 받는다.
const axiosGetPromise = generator.next().value;
// promise를 기다린다음 next안에 응답값을 넣어주어 response에 값을 넣는다.
axiosGetPromise.then(response => {
generator.next(response);
});
코드가 길어지긴 했지만 멋지게 동기적으로 보이게끔 바꾸었다.
next를 호출하는 부분을 함수로 바꾸어보자
function customAsync(generatorMaker) {
const generator = generatorMaker();
const promise = generator.next().value;
promise.then(response => {
generator.next(response);
});
}
customAsync(function*(){
const response = yield axios.get('url')'
console.log(response);
}
customAsync를 만들고 적어도 안에서 yield를 이용해 동기적으로 코드가 보이게끔 만들 수 있다.
yield가 두번이상 나온다면 위 코드는 의도데로 동작하지 않을 것이다. yield가 두번이상 나와도 가능하게 customAsync 코드를 변경해 보자
function customAsync(generatorMaker) {
const generator = generatorMaker();
function handle(next) {
const {value, done} = next;
// yield 가 더이상 없다면 제너레이터를 끝낸다.
if (done) return;
// yield가 더 있다면 promise를 기다린다.
// 기다린 response를 next에 넣어 yield의 응답값으로 넣어준다.
// handle에 next 반환값을 넣어 재귀호출 하여 yield 가 끝날 때 까지 실행한다.
return Promise.resolve(value).then(response => {
handle(generator.next(response));
})
}
handle(generator.next());
}
이제 customAsync만 있다면 yield 를 이용해 콜백을 await처럼 동작하게 할수 있다.
customAsync(function* () {
const a = yield Promise.resolve("abc");
console.log(a); // abc
const b = yield Promise.resolve("ddd");
console.log(b); // ddd
})
제너레이터를 이용하면 콜백지옥에서 해방될 수 있지만 위 customAsync 코드는 예외처리도 안되었고 무엇보다 오류의 처리를 안해서 야매로 만든 함수인 상태다
잘 만들어진 문법인 async/await 키워드를 사용하자
참고 자료
https://www.youtube.com/watch?v=IJ6EgdiI_wU
https://www.digitalocean.com/community/tutorials/understanding-generators-in-javascript
https://www.promisejs.org/generators/
https://velog.io/@ctdlog/JS-자바-스크립트의-비동기-처리-톺아보기-2-generator-asyncawait