queueMicrotask·Promise.then이 setTimeout(0)·동기 코드와 얽히는 걸 한 스텝씩 보는 시각화. 핵심: 마이크로태스크 큐는 매 턴 끝까지 비워진다 → then/queueMicrotask는 항상 setTimeout(0)보다 먼저고, 드레인 도중 추가된 중첩 마이크로태스크까지 같은 턴에 처리된다(중첩이 유한한 한 starvation 없음).
한 이벤트 루프 턴 = 네 단계:
- task queue에서 가장 오래된 매크로태스크 1개 실행
- microtask checkpoint — 큐가 빌 때까지 전부 드레인 (드레인 중 추가된 것 포함)
- 렌더링 갱신 (필요 시)
- 1로 복귀
그래서 한 턴의 트레이스 = [micro들] ++ [macro 하나] — 매크로는 맨 끝, 드레인 도중 끼어들 수 없다.
location.href setter의 실행 시점과 실제 navigation 처리 시점은 분리된다. setter는 동기적으로 정상 실행되지만(흐름을 끊지 않음), 그 효과는 브라우저 슬롯에 “마지막 값으로 덮어쓰기”로만 남는다. 실제 이동은 콜 스택이 비고 마이크로태스크까지 소진된 뒤(④)에야 일어난다.
그래서 “암묵적 await” 가설은 틀렸다 — await였다면 첫 줄에서 멈춰 google.com으로 갔겠지만, 실제로는 두 setter가 모두 실행되고 마지막 값 google2.com으로 간다.
location.href = 'https://google.com'
console.log('실행됨 1') // 출력됨
location.href = 'https://google2.com'
console.log('실행됨 2') // 출력됨 → await였다면 안 찍힘
두 console.log가 모두 찍히는 것이 setter가 흐름을 끊지 않는다는 직접 증거다.