@joe_777/async-relay
v0.1.0
Published
Lightweight async sequencing — concurrent work, ordered results.
Downloads
53
Maintainers
Readme
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 采用令牌传递模型:每个模块独立获取调度器令牌,并发执行任务,然后等待自己的轮次输出结果——无需了解其他模块的存在。
安装
npm install async-relay实际示例
三个独立模块,每个模块在独立文件中:
// 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
}// 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();
}// 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();
}// 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。使用方法
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
interface Scheduler {
// 当前一个任务调用 next() 后解析
previousComplete(): Promise<void>;
// 通知下一个任务可以开始输出阶段
next(): Promise<void>;
}type SchedulerName = string | symbol
许可证
MIT
