简易异步事件总线核心是让 emit 真正 await async 监听器完成,支持并行/串行执行、错误隔离、超时控制、abortsignal 取消、泛型类型校验、精确 off/once/destroy 生命周期管理。

构建一个简易的异步事件总线,核心不是让事件“变异步”,而是让事件触发时能自然等待监听器完成——尤其是当监听器本身是 async 函数时。它解决的是组件间通信中“发完就走”和“必须等结果”之间的张力。
监听器支持 async,emit 要真正 await
普通事件总线调用 fn(args) 就结束,但 async 监听器返回的是 Promise。如果 emit 不做处理,调用方根本不知道监听器是否执行完毕。
- 注册方式不变:仍用
on(event, handler),handler 可以是同步函数,也可以是async (data) => {...} - 触发逻辑升级:emit 收集所有匹配监听器,统一用
Promise.all(...)并行执行,或用for await (const fn of listeners) await fn(args)串行执行 - 避免静默失败:每个监听器包裹一层
fn().catch(() => {}),单个出错不影响其他监听器
加一层可控的超时与取消能力
真实场景里,某个监听器卡住(比如网络请求没设 timeout)会拖垮整个事件流。主动控制比被动等待更可靠。
- emit 支持传入
{ timeout: 3000 }:对每个监听器套Promise.race([fn(), new Promise((_, r) => setTimeout(() => r(new Error('timeout')), timeout))]) - 支持 AbortSignal:在调用前检查
signal.aborted,提前跳过;适合与 fetch、setTimeout 等原生可取消 API 配合 - 不强求所有监听器都响应取消,但提供统一入口,由上层决定是否启用
类型安全 + 生命周期管理
尤其在 TypeScript 项目中,松散的 any 类型会让事件参数逐渐失控,而长期运行的总线若不清除监听器,容易内存泄漏。
- 用泛型约束事件名和参数:定义
type Events = { 'user:login': [User]; 'order:paid': [Order, string] },让on<k extends keyof events>(event: K, fn: (args: Events[K]) => Promise<void>)</void></k>自动校验 - 提供
off(event, fn)精确移除,内部可用 WeakMap 存储 fn → listener 映射,避免引用残留 - 补充
once(event, fn)和destroy():前者自动在首次触发后 off;后者批量清理当前上下文所有订阅,适合组件卸载时调用
它不是替代,而是增强
这个总线不取代 DOM 事件或 CustomEvent,也不需要引入第三方库。它只是把原本“fire and forget”的事件机制,升级为可等待、可中断、可校验的通信管道。适用于状态同步、跨模块通知、副作用协调等需要明确执行反馈的场景。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











