Event Loop의 개념은 HTML 스펙에 잘 정리되어 있습니다. 문서에서 Event Loop, Task Queue의 개념에 대해 잘 정의되어 있는 것을 볼 수 있을 것입니다. 그런데 문서 중간에 MicroTask Queue(마이크로 태스크 큐)라는 생소한 용어가 존재합니다.

setTimeout(function() => {
	console.log('A');
}, 0);

Promise.resolve().then(() => {
	console.log('B');
}).then(() => {
	console.log('C');
});

콘솔에 찍히는 순서는 어떻게 될까요? Promise도 비동기로 실행된다고 할 수 있으니 Task Queue에 추가돼서 순서대로 A → B → C 가 될까요? 아니면 Promise는 setTimeout 처럼 최소단위 지연이 없으니 B → C → A 일까요? 체인 형태로 연속해서 호출된 then() 함수는 어떤 식으로 동작할까요?

결론부터 말하자면 정답은 B → C → A 입니다. 이유는 바로 프로미스가 MicroTask Queue를 사용하기 때문입니다. 그럼 MicroTask Queue가 대체 무엇인가?

*MicroTask Queue는 쉽게 말해 일반 태스크 보다 더 높은 우선순위를 갖는 태스크라고 할 수 있습니다. 즉, Task Queue에 대기중인 태스크가 있더라도 마이크로 태스크가 먼저 실행됩니다. 위의 예제를 통해 좀더 자세히 알아보겠습니다.*

*setTimeout() 함수는 콜백 A를 Task Queue에 추가하고, Promise의 then() 메소드는 콜백 B를 Task Queue가 아닌 별도의 MicroTask Queue에 추가합니다.*

위의 코드의 실행이 끝나면 태스크 Event Loop는 Task Queue 대신 MicroTask Queue가 비었는지 먼저 확인하고, 해당 큐에 있는 콜백 B를 실행합니다. 콜백 B가 실행되고 나면 두번째 then() 메소드가 콜백 C를 MicroTask Queue에 추가합니다. Event Loop는 다시 MicroTask Queue가 비었는지 확인하고, 큐에 있는 콜백 C를 실행합니다. 이후에 MicroTask Queue가 비었음을 확인한 다음 Task Queue에 콜백 A를 꺼내와 실행합니다.

잘 와 닿지 않는 분들은 이와 관련해서 인터랙션과 함께 아주 잘 정리된 글이 있습니다. (참고)


원문 글에서는 브라우저마다 Promise의 호출 순서가 다른 문제를 지적하고 있는데, 이유는 PromiseECMAScript에 정의되어 있는 반면, MicroTaskHTML 스펙에 정의되어 있는데, 둘의 연관관계가 명확하지 않기 때문이다.

(ECMAScript에는 ES6 부터 Promise를 위해 잡 큐(Job Queue)라는 항목이 추가되었지만, HTML 스펙의 MicroTask 와는 별도의 개념이다.)