macrotask와 microtask의 차이는 event loop가 처리하는 타이밍의 차이입니다. JavaScript는 먼저 현재 call stack의 동기 코드를 끝까지 실행하고, stack이 비면 다음 macrotask로 넘어가기 전에 microtask queue를 모두 비웁니다. 그래서 같은 흐름에서는 Promise.then 같은 microtask가 setTimeout(..., 0) 같은 timer callback보다 먼저 실행됩니다.
다만 microtask 안에서 microtask를 계속 추가하면 timer나 rendering이 밀릴 수 있기 때문에, 우선순위가 높다는 점과 starvation 가능성을 함께 봐야 합니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
microtask가 계속 추가되면 어떻게 되나요?
event loop는 macrotask로 넘어가기 전에 microtask queue를 비우려고 합니다. microtask 안에서 다시 microtask를 계속 추가하면 timer나 rendering이 늦어질 수 있습니다.
이런 상황을 microtask starvation처럼 설명할 수 있습니다.
setTimeout 0ms는 정말 바로 실행되나요?
아닙니다. 0ms는 최소 지연에 가깝고, 현재 call stack과 microtask 처리가 끝난 뒤 task로 실행됩니다. browser 정책에 따라 실제 지연도 달라질 수 있습니다.
그래서 “0ms = 즉시 실행”이라고 말하면 부정확합니다.
rendering은 microtask와 어떤 관계가 있나요?
브라우저는 task와 microtask 처리 사이에서 rendering 기회를 가질 수 있습니다. microtask가 너무 많이 쌓이면 rendering도 늦어질 수 있습니다.
성능 문제를 이야기할 때 긴 task와 microtask 남용을 함께 볼 수 있습니다.
async/await는 이 흐름과 어떻게 연결되나요?
await 이후의 코드는 Promise continuation으로 이어지므로 microtask 흐름과 연결해서 이해할 수 있습니다. await가 나오면 함수 실행이 잠시 나뉘고, 이후 코드는 Promise가 해결된 뒤 이어집니다.
출력은 보통 start, end, promise, timeout 순서입니다. 동기 코드가 끝난 뒤 microtask가 먼저 처리되고 timer callback은 그 다음 task에서 실행됩니다.
microtask는 “지금 작업이 끝난 직후 최대한 빨리 이어서 처리해야 하는 일”에 가깝습니다. Promise callback이 대표적입니다. macrotask는 timer, user interaction, network event처럼 event loop의 다음 task 단위로 처리되는 작업으로 설명할 수 있습니다.
브라우저와 Node.js는 세부 queue가 완전히 같지는 않습니다. 공통으로 볼 부분은 task와 microtask의 큰 우선순위와 흐름입니다.
한 줄 정리
동기 코드가 끝나면 microtask queue를 먼저 비우고, 그 다음 macrotask가 실행됩니다.