闭包实现的异步pubsub系统通过once返回promise、emit触发resolve,确保每个订阅独立等待首次事件;handlers用数组存resolve函数,emit时复制清空以避免竞态,支持一次性等待语义。

用闭包构建支持“异步结果订阅”的 PubSub 系统,核心在于:让订阅者能等待尚未产生的值,并在值就绪时被通知;同时保持状态私有、避免竞态、不依赖 this 或 class。关键不是把 Promise 传进去,而是让 on 方法返回一个 Promise,由 emit 决定它何时 resolve。
让 on 返回可等待的 Promise
普通 on 只注册回调,而“异步结果订阅”需要调用方能 await 一次性的结果。实现方式是:每次 on 某个事件名时,内部生成一个 Promise + resolve 函数,存入 handlers[event],并在首次 emit 该事件时触发 resolve。
- handlers 不再只存函数,而是存 { promise, resolve } 对象(或用 Map 存 resolve 引用)
- 同一事件名允许多个 on 调用,每个都获得独立 Promise —— 不共享、不覆盖
- emit 时遍历所有待 resolve 的项,逐个调用 resolve(data),并清空该 entry(或标记为已触发)
确保异步安全:避免重复 resolve 和竞态
Promise 一旦 resolve 就不可逆,但 emit 可能被多次调用,或多个订阅者并发 on 同一事件。需防止:第二次 emit 错误地再次 resolve 已完成的 Promise,或 on 后 emit 再 on 导致旧 resolve 被覆盖。
- 每个 on 创建的 Promise 对应唯一 resolve 函数,且只允许执行一次
- handlers[event] 推荐用数组存未 resolve 的 resolve 函数(而非 Promise 实例),emit 时 forEach 并清空数组
- 不缓存 data 到闭包里——emit 是源头,数据应随每次调用实时传入
支持“等待首个结果”与“等待下次结果”语义
实际场景中,有的逻辑只需等一次登录成功数据(once),有的需持续响应后续刷新(on)。闭包系统可通过两个方法区分:
- once(event):注册后自动 off,返回 Promise,resolve 后从 handlers[event] 中移除对应 resolve
- on(event):返回 Promise,但 resolve 后不清除——若需再次等待,需再次调用 on
- 也可统一用 on,靠用户手动 off,更正交;once 是语法糖
完整最小可用示例
以下是一个无依赖、纯闭包实现:
function createAsyncEventBus() {
const handlers = {};
return {
once(event) {
return new Promise(resolve => {
if (!handlers[event]) handlers[event] = [];
handlers[event].push(resolve);
});
},
emit(event, data) {
const resolves = handlers[event];
if (!resolves || !resolves.length) return;
// 复制并清空,避免 emit 中再调 on 导致混乱
const copy = [...resolves];
handlers[event] = [];
copy.forEach(resolve => resolve(data));
}
};
}
// 使用:
const bus = createAsyncEventBus();
bus.once('user').then(u => console.log('拿到用户:', u));
bus.emit('user', { id: 123, name: 'Alice' }); // 控制台立即输出










