FEInterview Prep

Tistory

브라우저가 자바스크립트 타이머를 스로틀링(throttle) 하는 이유는 무엇일까요?

setTimeout(0)이 실제로는 4ms로 throttle 되는 이유와, scheduler.postTask/MessageChannel.postMessage가 그 우회로로 떠오른 맥락을 정리한다.

2025-10-23·7분 읽기
브라우저JavaScript
원문 보기 ↗

핵심 요약

브라우저는 setTimeout/setInterval 의 최소 지연을 정책적으로 늘려 배터리·메인 스레드를 보호한다. 대안으로 MessageChannel.postMessage, window.postMessage, scheduler.postTask 가 있고 Chrome 139 기준 측정값은 setTimeout 4.2ms 대 나머지 0.0~0.05ms 수준이다. 단 Safari 는 setTimeout 26ms, MessageChannel 도 추가 throttle 이 걸려 신뢰도가 떨어진다. 결론: 필요할 때만 scheduler.postTask(권장) → MessageChannelwindow.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가 아닌가" 의 답은 이벤트 루프가 아니라 브라우저 정책이다.

setTimeoutclampingnesting levelbackground tab
자주 하는 오해

"이벤트 루프 때문에 약간 늦는다" 로 끝내는 것. 실제론 HTML 명세의 nesting ≥ 5 → 4ms, 백그라운드 탭 1s 같은 명시적 제약이라는 점을 짚어야 한다.

마이크로태스크 직후 매크로태스크가 필요하면 `scheduler.postTask` 가 정답

fake-indexeddb 는 모든 마이크로태스크 종료 직후 트랜잭션을 자동 커밋해야 했고, 브라우저에서는 setTimeout 으로 폴백하면 Chrome 에서 동일 작업이 300ms → 4.8s (16배) 로 느려졌다. scheduler.postTask 는 throttle 이 없고 렌더링 파이프라인과 정렬돼 가장 안정적이다.

scheduler.postTaskMessageChannelmicrotaskmacrotask
자주 하는 오해

setTimeout(fn, 0) 이 항상 즉시 실행된다고 가정하고 성능 회귀를 발견 못 함. setTimeout 의 4ms 클램프를 인지하고 폴백 체인을 명시해야 한다.

브라우저별 동작 차이가 크다 — Safari 는 추가 throttle 이 있다

동일 벤치마크에서 Chrome/Firefox 의 setTimeout 은 ~4ms, scheduler.postTask 는 ~0ms 였지만 Safari 의 setTimeout 은 26ms, MessageChannel 도 추가 throttle 이 걸려 다른 측정과 결과가 달랐다. 즉 "표준대로 동작한다" 라고 가정하면 안 된다.

SafariFirefoxChromethrottle
자주 하는 오해

한 브라우저(보통 Chrome)에서만 측정해 결정하는 것. 여러 엔진에서 측정하고 워스트 케이스를 기준으로 폴백을 선택해야 한다.

읽는 순서

  1. 1이론

    HTML 명세의 timer steps 섹션과 Chromium 1s 클램프 글을 읽고, "왜 throttle 이 도입되었나" 한 문단으로 정리한다.

  2. 2구현

    for (let i=0;i<10;i++) setTimeout(...)scheduler.postTask(...) 를 같은 코드에서 측정하고, Chrome / Safari 두 엔진에서 결과 비교.

  3. 3실무

    현재 프로젝트의 setTimeout(fn, 0)setTimeout(fn, 1) 사용처를 찾아, "마이크로태스크 직후 매크로태스크가 필요한가" 기준으로 queueMicrotask 또는 scheduler.postTask 로 치환 가능 여부를 분류.

  4. 4설명

    동료에게 "setTimeout(0) 은 왜 0ms 가 아닌가" 를 5분 안에 설명한다. 4ms / 1s / Safari 차이까지 말할 수 있다면 합격.

면접 연결 질문

medium`setTimeout(fn, 0)` 의 실제 지연이 0이 아닌 구체적 이유와 그 정책의 목적을 설명해 주세요.
힌트

[감점 답변] "이벤트 루프 때문이다"로만 끝나기. [좋은 답변] HTML 명세의 nesting level ≥ 5 → 4ms 클램프, 백그라운드 탭의 1초 클램프, 배터리/메인 스레드 보호라는 intervention 의도를 설명. 가능하면 setImmediate 가 표준에서 사라진 배경(W3C "Priority of Constituencies")까지 연결.

hard마이크로태스크가 모두 끝난 직후 한 번 작업을 돌리고 싶다. 후보 API 4개를 비교해 선택 근거를 말해 주세요.
힌트

[감점 답변] "setTimeout(0) 쓰면 된다". [좋은 답변] setImmediate(폐기) / MessageChannel.postMessage / window.postMessage / scheduler.postTask 를 throttle 여부, 우선순위 제어, 다른 스크립트와의 충돌 가능성으로 비교하고, 기본은 scheduler.postTask 우선·미지원시 MessageChannel 폴백으로 답변.

medium백그라운드 탭에서도 정확한 타이밍이 필요한 작업이 있다면 어떻게 설계하시겠습니까?
힌트

[감점 답변] "setInterval 사용". [좋은 답변] Web Worker / Shared Worker / Service Worker 로 메인 스레드 throttle 을 우회하거나, 서버 측 push (Web Push) 로 트리거하는 식으로 접근. 단순 UI 업데이트라면 Page Visibility API 로 활성 탭 진입 시 catch-up.

자기 점검

`setTimeout` 4ms 클램프가 발동하는 정확한 조건을 한 문장으로 말해 보세요.
nesting level54msHTML spec
자주 하는 오해

"항상 4ms" 라고 외우는 것. 실제로는 중첩 5회 이상 일 때부터 적용된다.

왜 `scheduler.postTask` 만 throttle 이 없을 가능성이 높은지, 설계 의도 관점에서 설명해 보세요.
interventionuser agentfootgunrendering pipeline
자주 하는 오해

"새 API 라서 빠르다" 가 아니라, 렌더링 파이프라인과 정렬된 우선순위 제어 API 라서 브라우저가 개입할 동기가 적은 것.