@siroi/async-scheduler-task
v1.0.0
Published
任务调度队列
Readme
@siroi/async-scheduler-task
AsyncScheduler 是一个高性能异步时间分片调度器,专为解决 JavaScript 主线程阻塞问题设计。它通过优先级队列和零延迟分片技术,确保高优先级任务(如用户交互)优先执行,同时将耗时操作拆分为小片段在空闲时间执行,从而保持应用流畅性。
核心特性
⚡ 帧级精准控制:默认每帧最多执行 16ms(60fps),避免阻塞浏览器渲染
🎯 多级别优先级:4 级优先级调度,保障用户交互(滚动/拖拽)优先执行
🚀 零延迟分片:基于 MessageChannel 实现主线程让出,比 setTimeout 快 3-5 倍
🛡️ 安全降级:MessageChannel 失效时自动回退到 setTimeout 兜底
🔧 灵活控制:支持任务取消、队列清空、强制刷新等精细化操作
📦 类型安全:完整 TypeScript 类型定义,适配现代前端工程化
🌱 轻量无冗余:仅依赖 @siroi/min-heap,无其他第三方依赖
适用场景
- 虚拟滚动列表(大数据量渲染)
- 复杂数据计算/转换(如 Excel 解析、大数据可视化)
- 高频率用户交互优化(滚动、拖拽、输入响应)
- 非关键任务延迟执行(预加载、埋点分析、缓存更新)
- 避免长任务导致的页面卡顿/无响应(Long Task 优化)
安装方法
使用 npm:
npm install @siroi/async-scheduler-task使用 yarn:
yarn add @siroi/async-scheduler-task使用 pnpm:
pnpm add @siroi/async-scheduler-task快速开始
基础用法
import { AsyncScheduler, TaskPriority } from '@siroi/async-scheduler-task';
// 创建调度器实例
const scheduler = new AsyncScheduler();
// 高优先级任务(滚动时触发)
scheduler.schedule(() => updateScrollPosition(), TaskPriority.IMMEDIATE);
// 普通任务
const taskId = scheduler.schedule(() => processLargeData());
// 取消任务(如果需要)
scheduler.cancelTask(taskId);
// 组件卸载时清理
scheduler.destroy();自定义配置(低端设备优化)
// 为低端设备调整配置
const scheduler = new AsyncScheduler({
frameTime: 25, // 降低帧率至40fps,增加每帧执行时间
yieldInterval: 10 // 延长降级方案的分片间隔
});API 文档
TaskPriority 枚举
任务优先级枚举,数值越小优先级越高。注意:不要随意增加新优先级级别,避免破坏调度平衡
| 优先级 | 数值 | 适用场景 | 特性 | |----------|------|------------------------|----------------------------------------| | IMMEDIATE| 0 | 滚动事件、关键用户交互 | 最高优先级,会中断当前执行帧,立即调度 | | HIGH | 1 | 拖选操作、输入响应 | 高优先级,当前帧内执行,不会中断IMMEDIATE任务 | | NORMAL | 2 | 常规数据处理、非关键更新 | 默认级别,在高优任务完成后执行 | | LOW | 3 | 预加载、分析埋点、缓存更新 | 低优先级,仅当没有其他任务时执行 |
AsyncScheduler 类
构造函数
new AsyncScheduler(options?: { frameTime?: number; yieldInterval?: number })创建调度器实例。
参数:
options.frameTime- 每帧最大执行时间(ms),默认 16ms(60fps)options.yieldInterval- 降级方案的分片间隔(ms),默认 5ms
示例:
// 标准配置
const scheduler = new AsyncScheduler();
// 低端设备优化配置
const lowEndScheduler = new AsyncScheduler({
frameTime: 25, // 40fps
yieldInterval: 10
});schedule
schedule(fn: () => void, priority: TaskPriority = TaskPriority.NORMAL): number提交任务到调度器。
执行顺序规则:
- 优先级高的先执行(IMMEDIATE > HIGH > NORMAL > LOW)
- 同优先级按提交顺序执行(FIFO)
参数:
fn- 任务执行函数(无参无返回值)priority- 任务优先级,默认 NORMAL
返回值: 任务唯一 ID(可用于取消)
示例:
// 高优先级任务
const highTaskId = scheduler.schedule(() => {
console.log('处理拖选操作');
}, TaskPriority.HIGH);
// 普通优先级任务(默认)
const normalTaskId = scheduler.schedule(() => {
console.log('处理常规数据');
});cancelTask
cancelTask(taskId: number): boolean取消指定 ID 的任务。
注意事项:
- 任务可能已在执行中(无法中断正在执行的函数)
- 仅能取消队列中等待执行的任务
- 返回 false 表示任务不存在或已被执行/取消
参数:
taskId- 通过 schedule 返回的任务 ID
返回值: 是否成功取消
示例:
const taskId = scheduler.schedule(() => processData());
// 取消任务
const isCancelled = scheduler.cancelTask(taskId);
console.log('任务是否取消成功:', isCancelled);clearQueue
clearQueue(): void清空调度器队列。
执行效果:
- 丢弃所有未执行任务
- 重置 yield 状态
- 不会中断正在执行的任务
适用场景:
- 组件卸载前清理
- 路由切换时重置
示例:
// 组件卸载时
componentWillUnmount() {
scheduler.clearQueue();
}flushQueue
flushQueue(): void立即同步执行所有剩余任务。
重要警告:
- 会阻塞主线程直到所有任务完成
- 破坏分片机制,仅用于极端场景
- 执行后自动清空队列并重置状态
典型场景:
- 应用卸载前的强制清理
- 测试环境同步验证
示例:
// 应用退出前确保所有任务完成
window.addEventListener('beforeunload', () => {
scheduler.flushQueue();
});destroy
destroy(): void销毁调度器实例。
必须执行的清理操作:
- 清空任务队列
- 关闭 MessageChannel 端口(防止内存泄漏)
- 重置内部状态
重要: 在组件卸载/页面关闭前调用,否则可能导致端口泄漏、内存泄漏或意外任务执行。
示例:
// React组件卸载时
useEffect(() => {
const scheduler = new AsyncScheduler();
return () => {
scheduler.destroy(); // 组件卸载时销毁调度器
};
}, []);任务优先级说明
优先级使用指南
| 优先级 | 使用场景示例 | 执行特性 | 注意事项 | |----------|------------------------|------------------------|------------------------------| | IMMEDIATE| 滚动位置更新、窗口大小调整 | 立即中断当前帧执行 | 避免长时间运行,应保持轻量 | | HIGH | 拖拽操作、输入框实时验证 | 当前帧内优先执行 | 控制执行时间在5ms内 | | NORMAL | 列表数据处理、UI非关键更新 | 按顺序执行 | 可包含稍复杂逻辑 | | LOW | 数据预加载、日志上报、缓存更新 | 仅在空闲时执行 | 可被高优先级任务抢占 |
优先级最佳实践
避免滥用高优先级:IMMEDIATE 和 HIGH 优先级应仅用于用户直接交互的场景,过多高优先级任务会导致普通任务饥饿
合理拆分大任务:对于超过 10ms 的操作,即使是高优先级也应拆分为多个任务
优先级递进使用:同一系列操作应使用不同优先级,如:
- 用户点击(IMMEDIATE)→ 数据验证(HIGH)→ 数据处理(NORMAL)→ 日志上报(LOW)
性能优化和降级策略
性能优化配置
设备适配
根据设备性能调整参数:
// 检测设备性能并动态调整
const isLowEndDevice = detectLowEndDevice(); // 自定义设备检测函数
const scheduler = new AsyncScheduler({
frameTime: isLowEndDevice ? 25 : 16, // 低端设备降低帧率
yieldInterval: isLowEndDevice ? 10 : 5 // 低端设备延长降级间隔
});任务拆分策略
将大型任务拆分为小型任务:
// 不佳:单个大任务
scheduler.schedule(() => {
for (let i = 0; i < 10000; i++) {
processItem(data[i]); // 可能阻塞主线程
}
}, TaskPriority.NORMAL);
// 优化:拆分为多个小任务
function processInChunks(data, chunkSize = 100) {
let index = 0;
function processChunk() {
const end = Math.min(index + chunkSize, data.length);
for (; index < end; index++) {
processItem(data[index]);
}
if (index < data.length) {
// 继续调度下一个分片
scheduler.schedule(processChunk, TaskPriority.NORMAL);
}
}
scheduler.schedule(processChunk, TaskPriority.NORMAL);
}
processInChunks(largeDataset);降级策略详解
AsyncScheduler 采用双重保障策略确保分片可靠性:
主方案:MessageChannel
- 通过 port2.postMessage 触发 port1.onmessage
- 在当前任务队列清空后立即执行(微任务之后)
- 延迟≈0ms(比 setTimeout 快 3-5 倍)
降级方案:setTimeout
- 当 MessageChannel 未触发时(罕见情况)
- 使用 yieldInterval(5ms) 作为兜底
性能对比
| 机制 | 平均延迟 | 稳定性 | 兼容性 | |----------------|----------|----------|------------| | MessageChannel | 0.1ms | ★★★★★ | 现代浏览器 | | setTimeout(0) | 4-8ms | ★★★☆☆ | 全平台 | | setTimeout(5) | 5-10ms | ★★★★☆ | 全平台 |
注意事项和最佳实践
使用注意事项
任务函数纯度:任务函数应避免修改共享状态,或确保线程安全
错误处理:任务函数内部应包含错误处理,调度器会捕获并打印错误但不会中断其他任务
// 推荐的任务错误处理
scheduler.schedule(() => {
try {
riskyOperation();
} catch (e) {
// 业务错误处理
handleError(e);
}
}, TaskPriority.NORMAL);任务执行时间:单个任务执行时间应控制在 frameTime(16ms) 以内,过长会导致卡顿
避免循环依赖:任务函数不应直接调度自身,可能导致无限循环
最佳实践
组件中使用
在 React/Vue 等组件化框架中使用时,应在组件卸载时清理:
// React Hook 示例
import { useRef, useEffect } from 'react';
import { AsyncScheduler } from '@siroi/async-scheduler-task';
function DataTable() {
const schedulerRef = useRef<AsyncScheduler | null>(null);
useEffect(() => {
// 创建调度器
schedulerRef.current = new AsyncScheduler();
// 组件卸载时清理
return () => {
schedulerRef.current?.destroy();
schedulerRef.current = null;
};
}, []);
// 使用调度器处理数据
const loadLargeData = (data) => {
schedulerRef.current?.schedule(() => {
processAndRenderData(data);
}, TaskPriority.NORMAL);
};
return <div>Data Table</div>;
}虚拟滚动实现
结合虚拟滚动的典型应用:
function renderVirtualList(items) {
const scheduler = new AsyncScheduler();
const container = document.getElementById('list-container');
let visibleRange = getVisibleRange();
// 监听滚动事件(高优先级)
container.addEventListener('scroll', () => {
const newRange = getVisibleRange();
if (newRange !== visibleRange) {
visibleRange = newRange;
// 立即更新可见区域
scheduler.schedule(() => updateVisibleItems(visibleRange), TaskPriority.IMMEDIATE);
}
});
// 预加载(低优先级)
scheduler.schedule(() => preloadItems(visibleRange.expand(5)), TaskPriority.LOW);
}大数据处理
处理大量数据时的优化策略:
function processLargeDataset(dataset) {
const scheduler = new AsyncScheduler();
const batchSize = 100; // 每批处理数量
let index = 0;
function processBatch() {
const end = Math.min(index + batchSize, dataset.length);
// 处理当前批次
for (; index < end; index++) {
processItem(dataset[index]);
}
// 更新进度(高优先级)
scheduler.schedule(() => {
updateProgress((index / dataset.length) * 100);
}, TaskPriority.HIGH);
// 如果还有数据,继续调度下一批
if (index < dataset.length) {
scheduler.schedule(processBatch, TaskPriority.NORMAL);
} else {
// 全部完成(高优先级)
scheduler.schedule(() => {
showCompletionMessage();
}, TaskPriority.HIGH);
}
}
// 开始处理第一批
scheduler.schedule(processBatch, TaskPriority.NORMAL);
}