Web APIs
, Event Table
, Callback Queue
, Event Loop
등으로 구성되며 JS 코드가 실행될 때 브라우저와의 동작은 아래 그림으로 표현할 수 있다.Web APIs: JS 엔진이 아니며 브라우저 API로 Call Stack에서 실행된 비동기 함수는 Web API(Browser API)인 timer를 호출하고, Web API는 콜백함수를 Callback Queue에 밀어 넣는다.
Event Table: 비동기적으로 실행된 콜백함수가 보관 되는 영역이다. Queue의 자료 구조를 따른다.
특정 event(timeout, click, mouse move 등등)가 발생했을 때 어떤 callback 함수가 호출되야 하는지를 알고 있는 자료구조이다.
위 코드에서 호출된 timer가 종료되면 event가 발생하게 되는데 이때 exec callback 함수가 실행되어야 한다는 것을 Event Table이 알고 있다.
Callback Queue: 이벤트 발생 시 실행해야 할 callback 함수가 Callback Queue에 추가된다.
Event Loop: Event Loop의 역할은 간단하다.
microtask 는 일반 task queue보다 순위를 우선적으로 가져간다.
즉, Microtask Queue가 전부 실행된 후 task queue가 실행된다.
주로 프라미스 잡(promise job)이 해당된다. Promise
api
예) Promise, async/await, process.nextTick, Object.observe, MutationObserver
requestAnimationFrame
과 같이 브라우저 렌더링과 관련된 task를 넘겨받는 Queue이다.
requestAnimationFrame
API가 실행되면 콜백이 Animation Frames으로 담긴다.
우선순위는 Microtask Queue > Animation Frames > Task Queue 순으로 실행된다.
이때 중요한 점은 Microtask Queue나 Animation Frames를 방문할 때는, 큐 안에 있는 모든 작업들을 수행하지만, Task Queue를 방문할 때는 한 번에 하나의 작업만 call stack으로 전달하고 다른 Queue를 순회한다.
let i = 0;
let start = Date.now();
function count() {
// CPU 소모가 많은 무거운 작업을 수행
for (let j = 0; j < 1e9; j++) {
i++;
}
alert("처리에 걸린 시간: " + (Date.now() - start) + "ms");
}
count();
let i = 0;
let start = Date.now();
function count() {
// 무거운 작업을 쪼갠 후 이를 수행 (*)
do {
i++;
} while (i % 1e6 != 0);
// 여기에 onclick 이벤트 라든지
if (i == 1e9) {
alert("처리에 걸린 시간: " + (Date.now() - start) + "ms");
} else {
setTimeout(count); // 새로운 호출을 스케줄링 (**)
}
}
count();
위 코드는 실행해도 브라우저 나머지 기능이 이상없이 동작하는데 그 이유는 (*)
로 표시한 do-while 반복에서 count 태스크 일부가 처리되고, 카운팅이 다 끝나지 않았다면 (**)
로 표시한 줄에서 카운팅 태스크가 다시 스케줄링 되기 때문이다.
부분 카운팅 실행 중간 중간에 '환기’를 해 줘서 이벤트 루프가 돌아갈 수 있게 해주면, 사용자 이벤트에 반응하면서 무거운 태스크 처리가 가능해진다.
하지만 두 코드의 시간차에 매우 큰데 이를 줄일려면 setTimeout을 앞으로 보내면 된다. 그래서 숫자를 세기 전에 스케쥴링을 하며 숫자를 세며 대기 시간을 소모할 수 있어 조금이라도 빠른 코드가 된다.
<div id="progress"></div>
<script>
let i = 0;
function count() {
// 무거운 작업을 쪼갠 후 이를 수행
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e7) {
setTimeout(count);
}
}
count();
</script>
menu.onclick = function() {
...
// 클릭한 메뉴 내 항목 정보가 담긴 커스텀 이벤트 생성
let customEvent = new CustomEvent("menu-open", {
bubbles: true
});
// 비동기로 커스텀 이벤트를 디스패칭
setTimeout(() => menu.dispatchEvent(customEvent));
};