Javascript에 대해 처음 배울 때, setTimeout
, setInterval
은 비동기 함수다. await 키워드는 비동기 함수 앞에 붙인다. 막연하게 이렇게 배웠던 것 같다.
오래 전에 써놓은 Promise.js의 코드를 보며 실행 순서를 머릿 속으로 그려보면서 내 지식을 점검하던 차였다.
async function foo() {
console.log(1);
await setTimeout(() => console.log(2), 10);
console.log(3);
}
function bar() {
console.log(4);
setTimeout(() => console.log(5), 100);
console.log(6);
}
async function main() {
console.log(7);
await setTimeout(foo, 0);
console.log(8);
await setTimeout(bar, 0);
}
main();
위 코드의 실행 순서를 정리하면 다음과 같다. 추가로 설명하려면 Javascript 엔진의 실행원리에 대한 설명이 필요하니 지금은 패스.
- main 함수를 호출해서 Call Stack에 올린다
- 호출된 main함수부터 실행. "7" 출력
setTimeout(foo, 0)
가 Task queue에 등록- "8" 출력
setTimeout(bar, 0)
가 Task queue에 등록, Task queue ={ foo, bar }
- main 함수 실행이 끝났음. 이제 queue에서 stack으로 가져올 작업을 확인. foo부터 가져옴
- foo에서 "1" 출력
setTimeout(() => console.log(2), 10)
이 Web API에서 처리되어 10ms 뒤에 Task queue로 이동, Task queue ={ bar, () => console.log(2)}
- "3" 출력 후 foo가 stack에서 제거, bar를 가져옴
- "4" 출력, Web API가 setTimeout 처리, 100ms 후에 콜백 함수가 Task queue로 이동, Task queue =
{() => console.log(2), () => console.log(5)}
- "6" 출력, bar 함수 실행이 끝났으므로 stack에서 제거
- Task queue에서 순차적으로 setTimeout의 콜백함수 실행, 10ms 후 "2" 출력, 100ms 후 "5" 출력
예상결과: 7 => 8 => 1 => 3 => 4 => 6 => 2 => 5
문득 코드를 보다가 await setTimeout과 그냥 setTimeout에 무슨 차이가 있나 하는 생각이 들었다. 저 코드를 작성할 당시에는 무작정 비동기 함수 = Promise라고 믿고 작성했었나 보다.
setTimeout의 반환값은 그저 timer를 식별해 나중에 clearTimeout에 사용하기 위한 timeoutID로, await 키워드를 붙여도 아무 의미가 없다. async/await 키워드와 함께 사용하고자 한다면 Promise 객체를 생성해줘야 한다.
async function foo() {
console.log(1);
}
async function main() {
console.log(7);
await new Promise((resolve, _) => {
setTimeout(() => {
foo();
return resolve();
}, 0);
});
console.log(8);
}
main();
이런 식으로 하면 실행 순서가 어떻게 될까? 실행 순서 내용이 중복될 수 있으니 코드를 일부 간소화했다.
- main 함수를 호출해서 Call Stack에 올린다
- 호출된 main함수부터 실행. "7" 출력
Promise((resolve, _) => { setTimeout(() => { foo(); return resolve(); }, 0); });
에서 Promise가 resolve 될 때까지 main 함수가 Micro task queue에 등록.- Promise 내부의 setTimeout이 Web API로 보내져 처리된 후 Task queue에 등록. foo가 호출된 후에야 Promise가 resolve되므로 "1" 출력 후에 "8" 출력.
의도한 대로 Timer가 기능하는 것을 알 수 있다. 단 resolve가 setTimeout 밖에 있거나 하면 setTimeout이 비동기로 처리되는 사이에 Promise가 resolve되어 순서가 바뀔 수 있음에 유의해야 한다.