npm package: @siroi/async-scheduler-task
Description: 任务调度队列
Author: siroi
Latest version: 1.0.0

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