React Fiber 风格任务调度库
React Fiber 风格任务调度库
下面是一个模仿 React Fiber 架构的任务调度库,它允许将大型任务分解为可增量执行的小单元,避免阻塞主线程。
安装
npm install fiber-scheduler
# 或
yarn add fiber-scheduler
核心代码
// fiber-scheduler.js// 任务优先级常量
const PriorityLevels = {IMMEDIATE: 0, // 最高优先级,需要同步执行USER_BLOCKING: 1, // 用户交互相关NORMAL: 2, // 普通优先级LOW: 3, // 低优先级IDLE: 4, // 空闲时执行
};// 默认配置
const defaultConfig = {frameTime: 16, // 每帧最大执行时间(ms)timeoutTime: 100, // 超时时间(ms)enableLogging: false, // 是否启用日志
};class FiberScheduler {constructor(config = {}) {this.config = { ...defaultConfig, ...config };this.taskQueue = [];this.currentTask = null;this.isPerformingWork = false;this.scheduledHostCallback = null;this.frameDeadline = 0;this.scheduled = false;// 绑定方法this.scheduleCallback = this.scheduleCallback.bind(this);this.unscheduleCallback = this.unscheduleCallback.bind(this);this.workLoop = this.workLoop.bind(this);this.requestHostCallback = this.requestHostCallback.bind(this);this.shouldYield = this.shouldYield.bind(this);// 初始化this._init();}_init() {// 设置消息通道用于调度const channel = new MessageChannel();this.port = channel.port2;channel.port1.onmessage = () => {if (this.scheduledHostCallback) {const currentTime = performance.now();const hasTimeRemaining = () => currentTime < this.frameDeadline;try {this.scheduledHostCallback(hasTimeRemaining, currentTime);} finally {this.scheduled = false;}}};}// 检查是否应该让出主线程shouldYield() {return performance.now() >= this.frameDeadline;}// 请求宿主回调requestHostCallback(callback) {this.scheduledHostCallback = callback;if (!this.scheduled) {this.scheduled = true;this.port.postMessage(null);}}// 工作循环workLoop(hasTimeRemaining, initialTime) {let currentTime = initialTime;// 设置当前帧的截止时间this.frameDeadline = currentTime + this.config.frameTime;// 处理当前任务if (this.currentTask === null) {// 从队列中获取优先级最高的任务this.currentTask = this.peek();}while (this.currentTask !== null) {if (this.shouldYield()) {// 时间片用完,暂停执行break;}const callback = this.currentTask.callback;if (typeof callback === 'function') {try {// 执行任务const continuationCallback = callback();currentTime = performance.now();if (typeof continuationCallback === 'function') {// 任务返回了延续函数,更新当前任务的回调this.currentTask.callback = continuationCallback;} else {// 任务已完成,从队列中移除this.pop();}} catch (error) {// 任务执行出错this.pop();throw error;}// 获取下一个任务this.currentTask = this.peek();} else {// 无效的回调,移除任务this.pop();this.currentTask = this.peek();}}// 如果还有任务,继续调度if (this.currentTask !== null) {this.requestHostCallback(this.workLoop);} else {this.isPerformingWork = false;}}// 获取最高优先级的任务peek() {return this.taskQueue[0] || null;}// 移除已完成的任务pop() {return this.taskQueue.shift();}// 将任务插入到队列中的正确位置(按优先级排序)push(task) {// 按优先级排序(数字越小优先级越高)for (let i = 0; i < this.taskQueue.length; i++) {if (task.priority < this.taskQueue[i].priority) {this.taskQueue.splice(i, 0, task);return;}}this.taskQueue.push(task);}// 调度回调函数scheduleCallback(priority, callback, options = {}) {const { timeout = this.config.timeoutTime } = options;const currentTime = performance.now();// 创建新任务const newTask = {callback,priority,startTime: currentTime,expirationTime: currentTime + timeout,id: Math.random().toString(36).substr(2, 9),};// 将任务添加到队列this.push(newTask);// 如果没有正在执行的工作,开始调度if (!this.isPerformingWork) {this.isPerformingWork = true;this.requestHostCallback(this.workLoop);}if (this.config.enableLogging) {console.log(`Scheduled task ${newTask.id} with priority ${priority}`);}// 返回取消函数return () => this.unscheduleCallback(newTask);}// 取消已调度的回调unscheduleCallback(task) {const index = this.taskQueue.findIndex(t => t.id === task.id);if (index !== -1) {this.taskQueue.splice(index, 1);if (this.config.enableLogging) {console.log(`Unscheduled task ${task.id}`);}}}// 批量调度多个任务scheduleBatch(priority, callbacks, options = {}) {const cancelFunctions = [];callbacks.forEach(callback => {const cancelFn = this.scheduleCallback(priority, callback, options);cancelFunctions.push(cancelFn);});// 返回批量取消函数return () => {cancelFunctions.forEach(cancelFn => cancelFn());};}
}// 导出库
module.exports = {FiberScheduler,PriorityLevels,
};
TypeScript 类型定义
// index.d.ts
declare module 'fiber-scheduler' {export type PriorityLevel = 0 | 1 | 2 | 3 | 4;export const PriorityLevels: {IMMEDIATE: PriorityLevel;USER_BLOCKING: PriorityLevel;NORMAL: PriorityLevel;LOW: PriorityLevel;IDLE: PriorityLevel;};export interface Task {id: string;callback: () => (void | (() => void));priority: PriorityLevel;startTime: number;expirationTime: number;}export interface SchedulerConfig {frameTime?: number;timeoutTime?: number;enableLogging?: boolean;}export interface ScheduleOptions {timeout?: number;}export class FiberScheduler {constructor(config?: SchedulerConfig);scheduleCallback(priority: PriorityLevel,callback: () => (void | (() => void)),options?: ScheduleOptions): () => void;scheduleBatch(priority: PriorityLevel,callbacks: Array<() => (void | (() => void))>,options?: ScheduleOptions): () => void;unscheduleCallback(task: Task): void;shouldYield(): boolean;}
}
使用示例
// 示例代码
const { FiberScheduler, PriorityLevels } = require('fiber-scheduler');// 创建调度器实例
const scheduler = new FiberScheduler({frameTime: 16, // 每帧16mstimeoutTime: 100, // 超时时间100msenableLogging: true, // 启用日志
});// 模拟一个耗时任务
function heavyTask(id, chunks = 10) {let currentChunk = 0;return function performChunk() {// 模拟处理数据块for (let i = 0; i < 1000000; i++) {// 模拟计算Math.sqrt(i) * Math.random();}currentChunk++;console.log(`Task ${id} completed chunk ${currentChunk}/${chunks}`);// 如果还有更多工作,返回延续函数if (currentChunk < chunks) {return performChunk;}// 任务完成console.log(`Task ${id} completed!`);};
}// 调度多个任务
const cancelTask1 = scheduler.scheduleCallback(PriorityLevels.NORMAL,heavyTask('A', 5)
);const cancelTask2 = scheduler.scheduleCallback(PriorityLevels.USER_BLOCKING,heavyTask('B', 3)
);// 批量调度任务
const cancelBatch = scheduler.scheduleBatch(PriorityLevels.LOW,[heavyTask('C', 2),heavyTask('D', 4),heavyTask('E', 3)]
);// 5秒后取消所有任务
setTimeout(() => {console.log('Cancelling all tasks...');cancelTask1();cancelTask2();cancelBatch();
}, 5000);
API 说明
FiberScheduler 类
构造函数
new FiberScheduler(config)
- 参数:
config.frameTime
: 每帧最大执行时间(ms),默认16msconfig.timeoutTime
: 任务超时时间(ms),默认100msconfig.enableLogging
: 是否启用日志,默认false
方法
-
scheduleCallback(priority, callback, options)
: 调度一个回调函数priority
: 优先级,使用PriorityLevels中的值callback
: 要执行的回调函数,可以返回一个延续函数options.timeout
: 任务超时时间(ms)- 返回: 取消该任务的函数
-
scheduleBatch(priority, callbacks, options)
: 批量调度多个回调函数- 参数同上,但callbacks是回调函数数组
- 返回: 取消所有批量任务的函数
-
unscheduleCallback(task)
: 取消已调度的任务 -
shouldYield()
: 检查是否应该让出主线程
PriorityLevels 常量
IMMEDIATE
(0): 最高优先级,需要同步执行USER_BLOCKING
(1): 用户交互相关NORMAL
(2): 普通优先级LOW
(3): 低优先级IDLE
(4): 空闲时执行
使用场景
- 大型计算任务分解:将耗时计算分解为小块,避免阻塞UI
- 动画和交互:确保用户交互始终有最高优先级
- 数据批量处理:处理大量数据时保持应用响应性
- 后台任务:在空闲时执行低优先级任务
注意事项
- 任务函数应该能够被中断和恢复,返回延续函数是实现这一点的关键
- 合理设置优先级,确保关键任务能够及时执行
- 注意任务超时设置,避免长时间运行的任务影响用户体验
- 在不需要时及时取消任务,释放资源
这个库提供了类似React Fiber的任务调度能力,可以帮助开发者更好地管理JavaScript执行,保持应用的响应性。