setTimeout
이나 XMLHttpRequest
같은 Web API는 JS 엔진이 아닌 외부에 정의되어 있다. 태스크 큐 같은 장치 또한 JS 엔진 외부에 구현되어 있다.libuv
라이브러리를 사용하며, 여기서 이벤트 루프를 제공한다.setTimeout
, Promise
등과 같은 기능을 제공한다.console.log(1);
setTimeout(() => console.log(2));
console.log(3);
// 실행 결과 : 1, 3, 2 순으로 출력
상단의 코드는 아래와 같은 과정을 통해 최종적으로 출력된다.
console.log(1)
함수가 Call Stack에 추가되고 이후 실행된다.setTimeout
함수가 Call Stack에 추가되어 실행되고, 이는 Web API 를 호출시킨다.console.log(3)
함수가 Call Stack에 추가되고 이후 실행된다.console.log(2)
함수가 추가되고 실행된다.function delay() {
for (var i = 0; i < 100000; i++);
}
function foo() {
delay();
bar();
console.log('foo!');
}
function bar() {
delay();
console.log('bar!');
}
function baz() {
console.log('baz!');
}
// 결과 : bar!, foo!, baz! 순으로 출력된다.
setTimeout(baz, 10);
foo();
setTimeout
함수가 실행된다면 브라우저에게 타이머 이벤트를 요청한 후 콜 스택에서 제거된다.foo
함수가 실행되고, 내부의 bar
함수가 실행되며 차례로 스택에 추가되었다가 제거된다.baz
함수가 스택에 즉시 추가되어 실행된다.믿음성 문제 란?
baz
는 우리가 설정했던 딜레이인 10ms 보다 더 늦게 실행될 것이다. setTimeout
함수가 호출된 이후 실행된 foo
함수가 처리되기까지 오랜 시간이 걸린다면 콜 스택이 비워지지 않아 baz
함수가 실행될 수 없기 때문이다. setTimeout
의 타이머가 항상 올바르게 작동한다는 보장이 없다. 이를 믿음성 문제 라고 한다.Task queues are sets, not queues, because the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.
setTimeout
이나 setInterval
같은 비동기 함수의 콜백 함수나 이벤트 핸들러 같은 Task가 보관되는 곳이다.Task Queue 에서 Task란 어떤 것들일까?
<script src="...">
)가 로딩될 때 이를 실행하는 작업.setTimeout
이나 setInterval
같은 비동기 함수로부터 인계 받은 콜백 함수를 실행하는 작업.mousemove
이벤트와 이벤트 핸들러를 실행하는 작업해당 섹션에는 잘못된 정보가 다량 포함되어 있습니다. 개인적인 조사를 통해 빠르게 수정하겠습니다.
Promise
, Object.observe
, MutationObserver
, process.nextTick
이 이에 속한다.Micro Task Check Point 란?
perfoming a microtask checkpoint
플래그가 false인지를 확인한다. (현재 Micro Task Queue 를 비우는 중인지 체크)perfoming a microtask checkpoint
를 true 으로 한다.perfoming a microtask checkpoint
플래그가 true 이기 때문에, 해당 작업이 이중으로 실행되는 것을 막는다. (false 일 경우에만 체크하기에)function a() {
console.log('a1');
b();
console.log('a2');
}
function b() {
console.log('b1');
c();
console.log('b2');
}
async function c() {
console.log('c1');
await d();
console.log('c2'); // 이후의 작업은 Micro Task Queue로 이전
}
function d() {
return new Promise(resolve => {
console.log('d1');
resolve();
console.log('d2');
}).then(() => console.log('then!')); // then Callback 은 Micro Task Queue로 이전
a();
// a1
// b1
// c1
// d1
// d2
// b2
// a2
// then!
// c2
console.log(a1)
실행 및 출력console.log(b1)
실행 및 출력 console.log(c1)
실행 및 출력 console.log(d1)
실행 및 출력console.log(d2)
실행 및 출력console.log(c2)
) 는 Micro Task Queue에 쌓임console.log(b2)
실행 및 출력console.log(a2)
실행 및 출력console.log(then!)
출력.console.log(c2)
실행 및 출력function a() {
console.log('a1');
b();
console.log('a2');
}
function b() {
console.log('b1');
c();
console.log('b2');
}
async function c() {
console.log('c1');
d();
console.log('c2');
}
function d() {
return new Promise(resolve => {
console.log('d1');
resolve();
console.log('d2');
}).then(() => console.log('then!'));
}
a();
// a1
// b1
// c1
// d1
// d2
// c2
// b2
// a2
// then!
console.log(a1)
실행 및 출력console.log(b1)
실행 및 출력 console.log(c1)
실행 및 출력 console.log(d1)
실행 및 출력console.log(d2)
실행 및 출력console.log(c2)
실행 및 출력 (await 키워드가 없어 후속 Task가 Micro Task Queue 로 인계되지 않음)console.log(b2)
실행 및 출력console.log(a2)
실행 및 출력console.log(then!)
출력.requestAnimationFrame
은 인자로 받은 콜백 함수들을 화면이 렌더링 되기 이전에 실행한다. 정확히는 resize, scroll 등의 이벤트가 발동된 후에 실행된다.requestAnimationFrame
은 다음 화면이 리렌더링 될 때 실행하고픈 Task를 예약하는 역할을 하며, 이러한 Task는 Animation Frame Queue 라는 별도의 공간에 저장된다.