React Fiber 风格任务调度库

发布于:2025-09-06 ⋅ 阅读:(13) ⋅ 点赞:(0)

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, // 每帧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),默认16ms
    • config.timeoutTime: 任务超时时间(ms),默认100ms
    • config.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): 空闲时执行

使用场景

  1. 大型计算任务分解:将耗时计算分解为小块,避免阻塞UI
  2. 动画和交互:确保用户交互始终有最高优先级
  3. 数据批量处理:处理大量数据时保持应用响应性
  4. 后台任务:在空闲时执行低优先级任务

注意事项

  1. 任务函数应该能够被中断和恢复,返回延续函数是实现这一点的关键
  2. 合理设置优先级,确保关键任务能够及时执行
  3. 注意任务超时设置,避免长时间运行的任务影响用户体验
  4. 在不需要时及时取消任务,释放资源

这个库提供了类似React Fiber的任务调度能力,可以帮助开发者更好地管理JavaScript执行,保持应用的响应性。


网站公告

今日签到

点亮在社区的每一天
去签到