Task Queue와 Event Loop

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!');
}

setTimeout(baz, 10);
foo();

이전 JavaScript와 Event Loop에서 설명한 코드입니다. 여기서 하나의 궁금증이 생기는데, setTimeout 함수를 통해 넘긴 baz 함수는 어떻게 foo 함수가 끝나자 마자 실행될 수 있을까요? 바로 이 역할을 하는 것이 Task QueueEvent Loop 입니다. Task Queue는 말 그대로 콜백 함수들이 대기하는 큐(FIFO) 형태의 배열이라 할 수 있고, Event Loop는 호출 스택이 비워질 때마다 큐에서 콜백 함수를 꺼내와서 실행하는 역할을 해 줍니다.

앞선 예제를 살펴보면, 코드가 처음 실행되면 이 코드는 “현재 실행중인 태스크(일, 작업)”이 됩니다. 코드를 실행하는 도중 10ms가 지나면 브라우저의 타이머가 baz 함수를 바로 실행하지 않고 Task Queue에 추가하게 됩니다. Event Loop는 “현재 실행중인 태스크(일, 작업)” 이 종료되자 마자 Task Queue 에서 대기중인 첫 번째 태스크를 실행할 것입니다.

*foo 함수가 실행을 마치고 호출 스택이 비워지면 현재 실행중인 태스크는 종료되며, 그 때 Event Loop가 Task Queue에 대기중인 첫 번째 태스크인 baz 함수를 실행해서 호출 스택에 추가하게 됩니다.*

MDN의 Event Loop 설명을 보면 왜 “Loop” 라는 이름이 붙었는지를 아주 간단한 가상코드로 설명하고 있습니다.

The event loop - JavaScript | MDN

while(queue.waitForaMessage()) { // Loop
	queue.processNextMessage();
}

위 코드의 waitForMessage() 메소드는 현재 실행중인 태스크(Call Stack)가 없을 때 다음 태스크가 큐에 추가될 때까지 대기하는 역할을 합니다.

(Call Stack이 비었는지 확인하면서, Call Stack이 비어있고 Task Queue가 비어있다면 큐에 태스크 추가하기 위해 대기하는 함수)

이런 식으로 루프(Loop)는 ‘현재 실행중인 태스크(Call Stack)가 없는지’ 와 ‘Task Queue에 태스크가 있는지’를 반복적으로 확인하는 것입니다. 이를 간단하게 정리하면 다음과 같습니다.

좀 더 명확하게 이해하기 위해 앞의 예제를 조금 바꿔보겠습니다.