이벤트 루프 한 턴 — 마이크로태스크가 setTimeout(0) 보다 항상 먼저다

queueMicrotask·Promise.thensetTimeout(0)·동기 코드와 얽히는 걸 한 스텝씩 보는 시각화. 핵심: 마이크로태스크 큐는 매 턴 끝까지 비워진다 → then/queueMicrotask는 항상 setTimeout(0)보다 먼저고, 드레인 도중 추가된 중첩 마이크로태스크까지 같은 턴에 처리된다(중첩이 유한한 한 starvation 없음).

한 이벤트 루프 턴 = 네 단계:

  1. task queue에서 가장 오래된 매크로태스크 1개 실행
  2. microtask checkpoint — 큐가 빌 때까지 전부 드레인 (드레인 중 추가된 것 포함)
  3. 렌더링 갱신 (필요 시)
  4. 1로 복귀

그래서 한 턴의 트레이스 = [micro들] ++ [macro 하나] — 매크로는 맨 끝, 드레인 도중 끼어들 수 없다.

#559

location.href 는 암묵적 await 가 아니다 — setter 는 동기, navigation 은 태스크

location.href에 값을 넣으면 그 줄에서 바로 페이지가 이동할 것 같다. 마치 **암묵적 await**처럼 거기서 멈춘다고 생각하기 쉽다. 그럼 뒤에 오는 코드는 실행되지 않을까.

location.href = 'https://google.com'
console.log('실행됨 1')
location.href = 'https://google2.com'
console.log('실행됨 2')

둘 다 출력된다. 그리고 이동은 google2.com으로 간다.

await였다면 첫 줄에서 멈춰 실행됨 1도 출력되지 않고 google.com으로 갔어야 한다. 두 결과가 모두 어긋나니 가설은 틀렸다.

실제로는 이렇다. setter는 동기적으로 정상 실행되고 흐름을 끊지 않는다. 문서를 언로드하고 네트워크 요청을 보내는 실제 작업은 태스크로 넘어가 지금 실행 중인 코드가 끝난 뒤에 시작된다. 그리고 두 번째 navigation이 시작되면 진행 중이던 첫 번째가 버려진다1 — 마지막 값이 이기는 건 덮어써서가 아니라 앞의 것이 취소되기 때문이다.

location.href setter가 실행되는 시점과 실제 navigation이 처리되는 시점이 분리되는 이벤트 루프 타임라인

Footnotes

  1. “Set the ongoing navigation for navigable to navigationId. This will have the effect of aborting other ongoing navigations of navigable, since at certain points during navigation changes to the ongoing navigation will cause further work to be abandoned.” — 그리고 문서 언로드·페치는 in parallel 로 넘어간다. (HTML Standard — navigate)

#569