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) {
    // 低优先级:压缩计算不阻塞 UI
    scheduler.postTask(
      async () => {
        const result = await compressImage(file, { quality: 0.85 });
        // 中优先级:更新 DOM 让用户看到进度
        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]));

    // 每 50 项让出一次,避免长任务
    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#优先级#主线程#任务调度