发布订阅模式通过事件中心解耦发布者与订阅者,核心是on/emit/off方法实现松耦合通信;可扩展once返回Promise支持await,适用于组件通信、状态管理、异步协调等场景。

发布订阅模式(Pub/Sub)在 JavaScript 异步编程中是一种解耦事件触发与响应逻辑的常用方式,它让多个模块无需直接引用彼此,就能通过统一的消息中心通信。核心在于“发布者不关心谁接收,订阅者不关心谁发布”,特别适合处理松耦合、多监听、跨组件或异步回调场景。
基础实现:一个轻量级 Event Emitter
用一个对象管理事件名与回调函数列表,支持 on(订阅)、emit(发布)、off(取消订阅) 三个基本方法:
```js
class EventEmitter {
constructor() {
this.events = {}; // { 'data:loaded': [fn1, fn2], 'error': [fn3] }
}
on(type, listener) {
if (!this.events[type]) this.events[type] = [];
this.events[type].push(listener);
}
emit(type, ...args) {
const handlers = this.events[type];
if (handlers && handlers.length) {
handlers.forEach(fn => fn(...args));
}
}
off(type, listener) {
const handlers = this.events[type];
if (handlers) {
this.events[type] = handlers.filter(fn => fn !== listener);
}
}
}
```
这个版本已可支撑多数前端场景。注意:
• 每次 emit 都是同步执行所有监听器,如需异步调度,可用 setTimeout(() => ..., 0) 或 Promise.resolve().then(...)
• 若需防止重复订阅,可在 on 中增加去重判断
• 订阅后未及时 off 可能导致内存泄漏,尤其在单页应用中组件销毁时应清理
立即学习“Java免费学习笔记(深入)”;
结合 Promise 实现异步事件流
当需要等待某个事件发生后再继续流程(比如“用户登录完成后再加载个人数据”),可封装返回 Promise 的 once 方法:
```js
EventEmitter.prototype.once = function(type) {
return new Promise(resolve => {
const handler = (...args) => {
this.off(type, handler);
resolve(...args);
};
this.on(type, handler);
});
};
// 使用示例
async function initApp() {
await eventBus.once('user:login');
loadUserProfile();
}
```
这种写法把事件驱动转为 await 友好风格,避免嵌套回调,也便于错误捕获(配合 try/catch)。关键点:
• once 内部自动清理监听器,确保只响应一次
• 若事件永远不触发,Promise 将一直 pending —— 建议搭配超时控制(如 Promise.race([once(), timeout(5000)]))
• 不适用于需多次响应的场景,此时仍用 on
实际应用场景举例
• 组件间通信:Vue/React 中父子组件无直接引用时,用全局 eventBus 传递状态变更(如弹窗关闭、表单提交成功)
• 状态管理辅助:Redux 或 Zustand 中,监听特定 action 类型做副作用(如日志上报、埋点)
• 异步任务协调:多个 API 并行请求完成后统一通知 UI 更新,用 emit('api:all-done') 触发后续逻辑
• 插件系统:主程序发布生命周期事件('beforeRender', 'afterMount'),插件按需订阅并注入行为
注意事项与优化方向
• 避免全局污染:优先使用模块级实例而非 window 上的全局 eventBus
• 支持通配符或命名空间:如 on('user:*') 或 on('auth.login.success'),提升事件组织能力
• 添加优先级或拦截机制:某些监听器可决定是否继续传播(类似 DOM 事件的 stopPropagation)
• 考虑使用成熟库:如 PubSubJS、eventemitter3,它们已处理边界情况(空事件、递归触发、性能优化等)











