npm package: @joe_777/async-relay
Description: Lightweight async sequencing — concurrent work, ordered results.
Latest version: 0.1.0

# async-relay

轻量级异步任务编排 —— 并发执行任务，有序输出结果。

## 问题背景

你的网页需要按顺序（A、B、C）渲染三个模块。每个模块独立从 API 获取数据并渲染。你希望：

- **并发获取数据** —— 不必等待 A 完成才开始 B 的数据获取
- **有序渲染输出** —— 始终按 A → B → C 的顺序渲染，无论哪个模块的数据先返回
- **模块完全解耦** —— A、B、C 是独立的模块，不需要相互了解

这比看起来要困难得多：

| 方案 | 并发获取数据 | 有序渲染 | 模块解耦 |
|---|---|---|---|
| 串行执行（依次 await） | ✗ | ✓ | ✓ |
| `p-queue`（并发数：1） | ✗ | ✓ | ✓ |
| `async-mutex` | ✗ | ✓ | ✓ |
| `Promise.all` + 共享状态 | ✓ | ✓ | ✗（模块需协调） |
| **async-relay** | **✓** | **✓** | **✓** |

`p-queue` 和 `async-mutex` 会序列化整个任务——如果你将获取数据+渲染作为一个整体加入队列，数据获取阶段就无法并发执行。`Promise.all` 虽然可以协调，但需要所有模块共享彼此的 Promise 引用，造成紧密耦合。

`async-relay` 采用令牌传递模型：每个模块独立获取调度器令牌，并发执行任务，然后等待自己的轮次输出结果——无需了解其他模块的存在。

## 安装

```bash
npm install async-relay
```

## 实际示例

三个独立模块，每个模块在独立文件中：

```typescript
// moduleA.ts
import { getScheduler } from 'async-relay';

export async function loadSectionA() {
  const s = getScheduler('page-init');

  const data = await fetchDataA();   // 立即开始，与其他模块并发执行

  await s.previousComplete();         // 等待轮到我渲染
  await renderSectionA(data);
  await s.next();                     // 传递令牌给模块 B
}
```

```typescript
// moduleB.ts
import { getScheduler } from 'async-relay';

export async function loadSectionB() {
  const s = getScheduler('page-init');

  const data = await fetchDataB();   // 与 A 和 C 并发执行

  await s.previousComplete();         // 等待 A 渲染完成
  await renderSectionB(data);
  await s.next();
}
```

```typescript
// moduleC.ts
import { getScheduler } from 'async-relay';

export async function loadSectionC() {
  const s = getScheduler('page-init');

  const data = await fetchDataC();   // 可能最先完成数据获取，但仍需等待渲染轮次

  await s.previousComplete();         // 等待 B 渲染完成
  await renderSectionC(data);
  await s.next();
}
```

```typescript
// page.ts —— 同时启动三个模块
import { loadSectionA } from './moduleA';
import { loadSectionB } from './moduleB';
import { loadSectionC } from './moduleC';

loadSectionA();
loadSectionB();
loadSectionC();
// 三个模块并发获取数据。
// 即使 C 的数据最先返回，渲染顺序仍是 A → B → C。
// 总耗时 ≈ max(fetchA, fetchB, fetchC)，而非 fetchA + fetchB + fetchC。
```

## 使用方法

```typescript
import { getScheduler, clearSchedulers } from 'async-relay';

async function task(id: string, delay: number) {
  const s = getScheduler('myQueue');

  // 主要工作并发执行
  await doWork(delay);

  // 等待前一个任务完成结果输出
  await s.previousComplete();
  await showResult(id);

  // 通知下一个任务可以开始输出结果
  await s.next();
}

// 三个任务同时启动
task('A', 10_000);
task('B', 5_000);
task('C', 0);     // C 最先完成工作，但最后输出结果

// 时间线：
// t=0s:  A、B、C 同时开始获取数据
// t=0s:  C 完成工作 —— 等待 A
// t=5s:  B 完成工作 —— 等待 A
// t=10s: A 完成工作 → 输出结果 → B 输出 → C 输出
// 总耗时：约 10s，而非 15s

clearSchedulers('myQueue');
```

## API

### `getScheduler(name?: SchedulerName): Scheduler`

创建并返回一个新的调度器令牌，该令牌会链接到同一 `name` 下先前创建的令牌。令牌按照 `getScheduler` 调用的顺序链接，从而确定输出顺序。

如果省略 `name`，将使用默认共享队列。

### `clearSchedulers(name?: SchedulerName): void`

移除所有注册在 `name` 下的调度器令牌，释放内存。

### `interface Scheduler`

```typescript
interface Scheduler {
  // 当前一个任务调用 next() 后解析
  previousComplete(): Promise<void>;

  // 通知下一个任务可以开始输出阶段
  next(): Promise<void>;
}
```

### `type SchedulerName = string | symbol`

## 许可证

MIT