可用queueMicrotask()或Promise.resolve().then()将事件分发推入微任务队列,确保当前同步代码执行完后立即、顺序一致地触发所有监听器,避免阻塞且优于setTimeout的宏任务延迟。

可以用 Promise.resolve().then() 或 queueMicrotask() 把事件分发逻辑推入微任务队列,确保在当前同步代码结束后、下一轮宏任务前执行订阅回调,避免同步阻塞,也保证响应顺序一致。
用 queueMicrotask 实现发布订阅
这是最直接的方式,语义清晰且原生支持:
- 每次
publish时不立即执行回调,而是用queueMicrotask推入微任务 - 所有订阅者会在当前调用栈清空后、渲染或定时器之前被统一触发
- 即使多次
publish,每个都会生成独立微任务,保持调用顺序
示例:
class EventEmitter {
constructor() {
this.subscribers = new Map();
}
subscribe(event, callback) {
if (!this.subscribers.has(event)) {
this.subscribers.set(event, []);
}
this.subscribers.get(event).push(callback);
}
publish(event, data) {
const callbacks = this.subscribers.get(event) || [];
// 微任务中分发,不阻塞当前同步流程
queueMicrotask(() => {
callbacks.forEach(cb => cb(data));
});
}
}
const ee = new EventEmitter();
ee.subscribe('click', (d) => console.log('A:', d));
ee.subscribe('click', (d) => console.log('B:', d));
console.log('before');
ee.publish('click', 'hello');
console.log('after');
// 输出顺序:before → after → A: hello → B: hello
用 Promise.resolve().then() 兼容旧环境
在不支持 queueMicrotask 的环境中(如较老版本 Safari),可用 Promise 微任务降级:
立即学习“Java免费学习笔记(深入)”;
- 原理相同:
Promise.then回调属于微任务 - 注意避免意外的异常穿透(可加
catch吞掉订阅者抛出的错误) - 不需要真正返回 Promise,仅利用其微任务调度能力
替换 publish 方法即可:
publish(event, data) {
const callbacks = this.subscribers.get(event) || [];
Promise.resolve().then(() => {
callbacks.forEach(cb => {
try {
cb(data);
} catch (e) {
console.error('Subscriber error:', e);
}
});
});
}
为什么不用 setTimeout(fn, 0)?
setTimeout 是宏任务,会延迟到下一轮事件循环,带来明显延迟:
- 两次
publish可能被拆到两个宏任务中,中间可能插入 UI 渲染、用户输入等 - 无法保证“同一事件的多个监听器按注册顺序紧挨着执行”
- 在动画帧或表单验证等场景下,响应滞后感明显
而微任务能聚合响应,更贴近“发布即排队、同步完成后立刻通知”的直觉。
注意事项与边界情况
- 微任务不会跨事件循环,如果在异步回调里 publish,它仍属于那个异步任务的微任务阶段
- 避免在订阅回调中又调用
publish形成无限微任务循环(需加节流或深度限制) - 若需同步触发(如调试或特殊控制流),应提供
publishSync方法作为补充 - 微任务本身不解决回调执行异常中断后续的问题,建议包裹
try/catch


















