React Fiber 风格任务调度库
下面是一个模仿 React Fiber 架构的任务调度库,它允许将大型任务分解为可增量执行的小单元,避免阻塞主线程。
安装
bash
npm install fiber-scheduler
# 或
yarn add fiber-scheduler
核心代码
javascript
// 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 类型定义
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;
}
}
使用示例
javascript
// 示例代码
const { FiberScheduler, PriorityLevels } = require('fiber-scheduler');
// 创建调度器实例
const scheduler = new FiberScheduler({
frameTime: 16, // 每帧16ms
timeoutTime: 100, // 超时时间100ms
enableLogging: 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执行,保持应用的响应性。