Scheduler API 實戰:瀏覽器任務優先級調度與主執行緒最佳化

性能优化(更新於 2026年6月12日)

主執行緒調度的困境

瀏覽器主執行緒同時承擔 UI 渲染、事件處理、JavaScript 執行。長任務(>50ms)會阻塞互動,導致 INP 指標劣化:

傳統方案 問題
setTimeout(fn, 0) 實際延遲 4ms,無法控制優先級
requestIdleCallback 只在空閒時執行,無法保證時效
await scheduler.yield() ✅ 精確讓出,保持調度順序
scheduler.postTask() 優先級 + 讓出 + 取消,完整調度

Scheduler API 優先級模型

scheduler.postTask() 提供三級優先級,對應到瀏覽器內部調度:

優先級 含義 典型用途 對應 Chrome 內部
user-blocking 最高,立即執行 使用者輸入響應、動畫 Very High
user-visible 中等,盡快執行 DOM 更新、資料解析 High / Medium
background 最低,空閒執行 日誌上報、預計算 Low / Best Effort

基本用法

const result = await scheduler.postTask(
  () => heavyComputation(data),
  { priority: 'background' }
);

實戰:圖片壓縮任務的優先級調度

工具庫 圖片壓縮 處理批量圖片時,需要同時響應使用者操作:

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();

避免 Long Task 的策略組合

策略 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 只在瀏覽器空閒時執行,適合完全不緊急的工作(如預載入)。postTaskbackground 優先級類似但更可控,且支援取消和動態優先級調整。

yield() 會增加總耗時嗎?

單次 yield() 開銷約 0.1ms。每 50 項 yield 一次處理 10000 項,額外開銷僅 20ms,遠小於避免的阻塞時間。

如何除錯優先級?

Chrome DevTools → Performance 面板中,任務標註了優先級資訊。也可用 scheduler.postTask(() => console.trace(), { priority: 'background' }) 追蹤調度。


總結

Scheduler API 為瀏覽器主執行緒提供了精確的優先級調度能力。scheduler.postTask() 實現三級優先級任務分發,scheduler.yield() 精確讓出主執行緒避免長任務,TaskController 支援取消與動態優先級調整。這是建構高效能、高響應 Web 應用的關鍵基礎設施。

本站提供瀏覽器本地工具,免註冊即可試用 →

#Scheduler API#postTask#优先级#主线程#任务调度