Scheduler API実践:ブラウザタスク優先度スケジューリングとメインスレッド最適化
メインスレードスケジューリングのジレンマ
ブラウザのメインスレッドはUIレンダリング、イベント処理、JavaScript実行を同時に担います。長時間タスク(>50ms)はインタラクションをブロックし、INP指標を悪化させます:
| 従来手法 | 問題 |
|---|---|
| setTimeout(fn, 0) | 実際の遅延4ms、優先度制御不可 |
| requestIdleCallback | アイドル時のみ実行、時効性保証なし |
| await scheduler.yield() | ✅ 精密な譲渡、スケジューリング順序を維持 |
| scheduler.postTask() | ✅ 優先度 + 譲渡 + キャンセル = 完全なスケジューリング |
Scheduler API優先度モデル
scheduler.postTask()は3段階の優先度を提供し、ブラウザ内部スケジューリングにマッピングされます:
| 優先度 | 意味 | 典型的用途 | Chrome内部マッピング |
|---|---|---|---|
user-blocking |
最高、即時実行 | ユーザー入力応答、アニメーション | Very High |
user-visible |
中程度、早期実行 | DOM更新、データ解析 | High / Medium |
background |
最低、アイドル時実行 | ログ送信、事前計算 | Low / Best Effort |
基本的な使い方
const result = await scheduler.postTask(
() => heavyComputation(data),
{ priority: 'background' }
);
実践:画像圧縮タスクの優先度スケジューリング
ToolsKuの画像圧縮はバッチ画像処理中もユーザー操作に応答する必要があります:
async function handleBatchCompress(files: File[]) {
const controller = new TaskController({ priority: 'background' });
for (const file of files) {
scheduler.postTask(
async () => {
const result = await compressImage(file, { quality: 0.85 });
await scheduler.postTask(
() => updateProgressUI(file.name, result),
{ priority: 'user-visible' }
);
},
{ signal: controller.signal }
);
}
return controller;
}
ユーザーが「キャンセル」をクリックすると、controller.abort()が保留中の全タスクを即座に停止します。
scheduler.yield():精密なメインスレッド譲渡
scheduler.yield()はsetTimeout(fn, 0)より精密です。より高優先度のタスクに譲渡しつつ、現在のタスクの実行順序を維持します:
async function processLargeDataset(items: Item[]) {
const results: Result[] = [];
for (let i = 0; i < items.length; i++) {
results.push(transform(items[i]));
if (i % 50 === 0) {
await scheduler.yield();
}
}
return results;
}
yield() vs setTimeout()の比較
| 特性 | setTimeout(fn, 0) | scheduler.yield() |
|---|---|---|
| 最小遅延 | 4ms | 0ms |
| 優先度認識 | ❌ なし | ✅ 高優先度に譲渡 |
| 実行順序 | 保証なし | ✅ スケジューリング順序を維持 |
| 長時間タスク分割 | ✅ | ✅ |
| ブラウザサポート | 全部 | Chrome 115+ |
TaskController:キャンセルと動的優先度
const controller = new TaskController({ priority: 'user-visible' });
scheduler.postTask(() => renderChart(data), { signal: controller.signal });
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
controller.setPriority('background');
} else {
controller.setPriority('user-visible');
}
});
controller.abort();
長時間タスク回避の戦略組み合わせ
戦略1:チャンク + yield
async function chunkedProcess<T>(items: T[], fn: (item: T) => void, chunkSize = 20) {
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
chunk.forEach(fn);
await scheduler.yield();
}
}
戦略2:優先度レイヤリング
function scheduleWork(work: WorkItem) {
const priority = work.type === 'user-input' ? 'user-blocking'
: work.type === 'render' ? 'user-visible'
: 'background';
return scheduler.postTask(work.execute, { priority });
}
戦略3:AbortController統合
const ac = new AbortController();
scheduler.postTask(() => mergePDFs(files), {
priority: 'background',
signal: ac.signal
});
window.addEventListener('beforeunload', () => ac.abort());
パフォーマンス実測:PDF結合シナリオ
PDF結合で20個のPDFファイルを処理したベンチマーク:
| 手法 | 総所要時間 | INP (ms) | メインスレッドブロック |
|---|---|---|---|
| 同期ループ | 3.2s | 480 | 3.2s |
| setTimeoutチャンク | 3.3s | 120 | 0.4s |
| postTask + yield | 3.2s | 60 | 0.1s |
総所要時間は変わらず、INPは480msから60msに低下—インタラクション応答性が8倍改善。
互換性とフォールバック
async function yieldToMain() {
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
return new Promise(resolve => setTimeout(resolve, 0));
}
}
function postTask(fn: () => void, options?: { priority?: string }) {
if ('scheduler' in window) {
return scheduler.postTask(fn, options as any);
}
return Promise.resolve(fn());
}
よくある質問
postTaskとrequestIdleCallbackのどちらを選ぶか?
requestIdleCallbackはブラウザがアイドル時のみ実行—全く緊急でない作業(プリロード等)に適しています。postTaskのbackground優先度は類似していますが、より制御可能でキャンセルや動的優先度調整をサポートします。
yield()は総所要時間を増やすか?
単一のyield()のコストは約0.1ms。50項目ごとにyieldして10000項目を処理する場合、追加オーバーヘッドはわずか20msで、回避したブロック時間より遥かに小さいです。
優先度のデバッグ方法?
Chrome DevTools → Performanceパネルでタスクに優先度情報が表示されます。scheduler.postTask(() => console.trace(), { priority: 'background' })でスケジューリングをトレースすることも可能です。
まとめ
Scheduler APIはブラウザメインスレッドに精密な優先度スケジューリング能力を提供します。scheduler.postTask()は3段階優先度のタスクディスパッチを実現し、scheduler.yield()は長時間タスクを回避する精密なメインスレッド譲渡を提供し、TaskControllerはキャンセルと動的優先度調整をサポートします。これは高性能・高応答性Webアプリケーション構築の必須インフラです。
ブラウザローカルツールを無料で試す →