ブラウザがJavaScriptタイマーをスロットリングする理由
(nolanlawson.com)- JavaScriptで
setTimeout(0)は実際には即時実行されず、最小 4ms の遅延が入ることが多いが、これは乱用防止のためのブラウザの基本的な制限である - こうした制約は、Webサイトがタイマーを無秩序に乱用して バッテリー消費 や インタラクション低下 を引き起こすのを防ぐための措置であり、バッテリーモードでは 16ms、バックグラウンドタブでは 1 秒へとさらに厳しく制限されることもある
- 開発者は
setTimeoutの限界を回避するため、setImmediate、MessageChannel.postMessage、window.postMessage、scheduler.postTaskなど、さまざまな 代替タイマー API を活用してきた - 実際のベンチマークでは、Chrome と Firefox では 4ms クランプが適用される一方、
MessageChannelとscheduler.postTaskはほぼ遅延なく動作し、Safari はsetTimeoutをより強く制限する特徴を示した - 根本的には、ユーザー体験の保護と開発者の自由のあいだのバランスの問題であり、現在は Scheduler API が標準化された解決策として定着しつつあるが、乱用が発生すれば新たな ブラウザ介入 (Intervention) が導入される可能性もある
setTimeout 制限の背景
setTimeout(0)であっても、乱用 のため実際の実行は最小 4ms 後になることが多いconst start = performance.now() setTimeout(() => { // 約4ms後に実行される console.log(performance.now() - start) }, 0)- これは無秩序な反復呼び出しを防ぎ、バッテリー消費とレンダリング遅延を減らすためである
- 一部のブラウザは環境に応じて制限を強化する
- バッテリーモード: 旧 Edge では 16ms
- バックグラウンドタブ: Chrome では最大 1 秒まで遅延
別のタイマー API の登場
setImmediate: IE と旧 Edge でのみサポートされ、事実上廃止MessageChannel.postMessage: 別チャネルを通じてイベントループに処理を渡すwindow.postMessage: 性能は良いが、他のスクリプトと衝突するリスクがあるscheduler.postTask: 最新ブラウザでサポートされ、最も安定した選択肢として評価されている
ベンチマーク結果 (MacBook Pro 2021, 101回反復測定)
- Chrome 139:
setTimeout4.2ms,scheduler.postTask0ms - Firefox 142:
setTimeout4.72ms,scheduler.postTask0.01ms - Safari 18.4:
setTimeout26.73ms,MessageChannel0.52ms,window.postMessage0.05ms
fake-indexeddb の事例
- IndexedDB は、イベントループのマイクロタスク終了直後にトランザクションの自動コミットを必要とする
- Node.js の
setImmediateは理想的だが、ブラウザではsetTimeoutが非効率的である - Chrome では 300ms で終わる作業が、ブラウザでは 4.8 秒まで伸びる問題が発生
- 解決策として
scheduler.postTaskをデフォルトで使い、互換性のためMessageChannel/window.postMessageをフォールバックとして採用した
ブラウザ介入をめぐる議論
- 一方は、タイマーを制限すべきであり、それによって開発者が自分自身から守られると主張する
- 他方は、開発者が自ら測定・最適化できるよう 自由を保証 すべきだと主張する
- 結局、ユーザー優先の原則に従い、ブラウザは乱用防止のために介入 (intervention) する
- Scheduler API は両者の立場を折衷し、開発者に細かな タスク制御権 を与えつつ、ブラウザのレンダリングパイプラインと整合するよう設計されている
今後の見通し
postTaskとpostMessageは当面スロットリングなしで維持される見込み- ただし、
user-blockingのような高優先度を乱用すれば、再び介入される可能性がある - 長期的には、別の
scheduler2のような代替 API が再び必要になるかもしれない
まだコメントはありません。