Tistory
브라우저가 자바스크립트 타이머를 스로틀링(throttle) 하는 이유는 무엇일까요?
setTimeout(0)이 실제로는 4ms로 throttle 되는 이유와, scheduler.postTask/MessageChannel.postMessage가 그 우회로로 떠오른 맥락을 정리한다.
핵심 요약
브라우저는 setTimeout/setInterval 의 최소 지연을 정책적으로 늘려 배터리·메인 스레드를 보호한다. 대안으로 MessageChannel.postMessage, window.postMessage, scheduler.postTask 가 있고 Chrome 139 기준 측정값은 setTimeout 4.2ms 대 나머지 0.0~0.05ms 수준이다. 단 Safari 는 setTimeout 26ms, MessageChannel 도 추가 throttle 이 걸려 신뢰도가 떨어진다. 결론: 필요할 때만 scheduler.postTask(권장) → MessageChannel → window.postMessage 순으로 폴백.
타이머 throttle은 "브라우저가 사용자(에이전시)를 대신해 footgun API를 막는 개입(intervention)"이다. 즉 setTimeout은 빠른 도구이지만 자주 오용되어 4ms·1초 등으로 강제 지연되고, 같은 일을 하지만 오용 가능성이 낮은 새 API(scheduler.postTask)는 throttle 없이 두는 방식으로 균형을 잡는다.
"왜 setTimeout(0)이 0ms가 아닌가"는 단골 질문이다. 단순히 "이벤트 루프가 비동기라서"로 답하면 약하다. 본질은 HTML 명세의 nesting level ≥ 5 시 4ms 클램핑, 백그라운드 탭 1초 클램핑, Safari의 더 강한 throttle 같은 정책 결정이고, 이것을 알면 IndexedDB autocommit 같은 마이크로태스크 직후 작업에 왜 setTimeout을 쓰면 16배 느려지는지까지 설명할 수 있다.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
`setTimeout(0)` 은 0ms 가 아니다 — nesting/포커스/배터리 정책이 끼어든다
HTML 명세상 setTimeout 이 5번 이상 중첩되면 4ms 로 클램핑되고, 백그라운드 탭은 Chrome 기준 1초 까지 늘어난다. 즉 "왜 0ms가 아닌가" 의 답은 이벤트 루프가 아니라 브라우저 정책이다.
"이벤트 루프 때문에 약간 늦는다" 로 끝내는 것. 실제론 HTML 명세의 nesting ≥ 5 → 4ms, 백그라운드 탭 1s 같은 명시적 제약이라는 점을 짚어야 한다.
마이크로태스크 직후 매크로태스크가 필요하면 `scheduler.postTask` 가 정답
fake-indexeddb 는 모든 마이크로태스크 종료 직후 트랜잭션을 자동 커밋해야 했고, 브라우저에서는 setTimeout 으로 폴백하면 Chrome 에서 동일 작업이 300ms → 4.8s (16배) 로 느려졌다. scheduler.postTask 는 throttle 이 없고 렌더링 파이프라인과 정렬돼 가장 안정적이다.
setTimeout(fn, 0) 이 항상 즉시 실행된다고 가정하고 성능 회귀를 발견 못 함. setTimeout 의 4ms 클램프를 인지하고 폴백 체인을 명시해야 한다.
브라우저별 동작 차이가 크다 — Safari 는 추가 throttle 이 있다
동일 벤치마크에서 Chrome/Firefox 의 setTimeout 은 ~4ms, scheduler.postTask 는 ~0ms 였지만 Safari 의 setTimeout 은 26ms, MessageChannel 도 추가 throttle 이 걸려 다른 측정과 결과가 달랐다. 즉 "표준대로 동작한다" 라고 가정하면 안 된다.
한 브라우저(보통 Chrome)에서만 측정해 결정하는 것. 여러 엔진에서 측정하고 워스트 케이스를 기준으로 폴백을 선택해야 한다.
읽는 순서
- 1이론
HTML 명세의 timer steps 섹션과 Chromium 1s 클램프 글을 읽고, "왜 throttle 이 도입되었나" 한 문단으로 정리한다.
- 2구현
for (let i=0;i<10;i++) setTimeout(...)와scheduler.postTask(...)를 같은 코드에서 측정하고, Chrome / Safari 두 엔진에서 결과 비교. - 3실무
현재 프로젝트의
setTimeout(fn, 0)과setTimeout(fn, 1)사용처를 찾아, "마이크로태스크 직후 매크로태스크가 필요한가" 기준으로queueMicrotask또는scheduler.postTask로 치환 가능 여부를 분류. - 4설명
동료에게 "
setTimeout(0)은 왜 0ms 가 아닌가" 를 5분 안에 설명한다. 4ms / 1s / Safari 차이까지 말할 수 있다면 합격.
면접 연결 질문
[감점 답변] "이벤트 루프 때문이다"로만 끝나기. [좋은 답변] HTML 명세의 nesting level ≥ 5 → 4ms 클램프, 백그라운드 탭의 1초 클램프, 배터리/메인 스레드 보호라는 intervention 의도를 설명. 가능하면 setImmediate 가 표준에서 사라진 배경(W3C "Priority of Constituencies")까지 연결.
[감점 답변] "setTimeout(0) 쓰면 된다". [좋은 답변] setImmediate(폐기) / MessageChannel.postMessage / window.postMessage / scheduler.postTask 를 throttle 여부, 우선순위 제어, 다른 스크립트와의 충돌 가능성으로 비교하고, 기본은 scheduler.postTask 우선·미지원시 MessageChannel 폴백으로 답변.
[감점 답변] "setInterval 사용". [좋은 답변] Web Worker / Shared Worker / Service Worker 로 메인 스레드 throttle 을 우회하거나, 서버 측 push (Web Push) 로 트리거하는 식으로 접근. 단순 UI 업데이트라면 Page Visibility API 로 활성 탭 진입 시 catch-up.
자기 점검
"항상 4ms" 라고 외우는 것. 실제로는 중첩 5회 이상 일 때부터 적용된다.
"새 API 라서 빠르다" 가 아니라, 렌더링 파이프라인과 정렬된 우선순위 제어 API 라서 브라우저가 개입할 동기가 적은 것.