Menu

Node.js EventEmitter:on、emit、once 与自定义事件

Node 的 events 模块中的 EventEmitter 让一个对象可以用 emit 发出具名事件,让其他代码用 on 或 once 做出响应。监听器按添加的顺序同步执行。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

EventEmitter 是 Node events 模块中的一个类,让对象可以发出具名事件。你用 emitter.on('name', fn) 注册监听器,用 emitter.emit('name', ...args) 触发它;该事件名下的每个监听器都会按添加的顺序执行,并收到你传入的参数。HTTP 服务器、流和 process 都是 EventEmitter,所以这几个方法在 Node 中处处通用。

两个监听器都在 before emit 和 after emit 之间执行:emit 同步调用它们,等它们执行完才返回。把两个 shop.on 调用互换位置,中间两行的输出也会互换。

on、once 与 off

on 会一直保留监听器,直到你移除它;once 在第一次调用后就移除监听器;off(和 removeListener 相同)用来移除一个监听器。要移除监听器,你需要传入当初添加的同一个函数,所以要给它起个名字,而不是直接内联写一个箭头函数。

first open ever 只打印一次,在 Ada 之后。logOpen 被移除后就没有任何监听器了,所以最后一次 emit 返回 false;调用 door.removeAllListeners('open') 则可以一次清除这两个监听器。

方法作用
on(name, fn)、addListener每次 emit 都调用 fn
once(name, fn)只在下一次 emit 时调用 fn
prependListener(name, fn)和 on 一样,但在已有监听器之前执行
off(name, fn)、removeListener移除该函数
emit(name, ...args)调用监听器,有监听器时返回 true
listenerCount(name)该事件有多少个监听器

error 事件

'error' 是特殊的。如果你发出这个事件却没有任何监听器,emit 会抛出这个错误;在真实的程序中,未捕获的错误会让进程崩溃。对于可能失败的 emitter,比如流和服务器,一定要添加 'error' 监听器。

没有监听器时,emit 会抛出错误,被 catch 捕获;添加了监听器之后,同样的 emit 只是调用它,程序会继续运行。其他任何没有监听器的事件名都会被静默忽略。

在自己的类中继承 EventEmitter

常见的用法是让你的类继承 EventEmitter,在构造函数中调用 super(),在发生某件事时调用 this.emit。调用者就可以像订阅任何 Node 对象一样订阅你的对象。

它依次打印 25%、50%、75%、100%,然后是 photo.png uploaded。把 up.start() 移到两行 up.on 之前,就什么都不会打印:事件在有人监听之前就已经触发了。

在构造函数中发出事件也会掉进同样的陷阱。必须立即发出事件的库会把调用包在 process.nextTick 里,它会在调用者的 on 代码之后执行(参见事件循环页面)。

用 events.once 等待事件

events.once(emitter, name) 返回一个 Promise,在该事件下一次被发出时,以数组形式 resolve 出事件的参数。它把一次性事件变成了可以 await 的东西;如果 emitter 先发出了 'error',它就会 reject。

await 会暂停,直到定时器发出 ready,然后数组被解构成两个参数。对于会多次触发的事件,events.on(emitter, name) 会返回一个异步迭代器,你可以用 for await 遍历它。

常见错误

在频繁执行的函数中添加监听器。 在每个请求处理函数中调用 on,会让监听器和内存不断累积。只添加一次,或者使用 once。

移除内联函数。 off('data', () => ...) 什么都不会移除,因为这个箭头函数是一个新函数。请保留你添加的那个函数的引用。

以为监听器会稍后执行。 emit 是同步的,所以一个很慢的监听器会拖住发出事件的代码。把监听器标记为 async 也没用,因为在第一个 await 之前,它的代码仍然在同一个线程上运行。把繁重的工作拆成几块,中间用 setImmediate 隔开,或者把它移到 worker 线程中。

常见问题

EventEmitter 是异步的吗?

不是。emit 会立即依次调用每个监听器,等最后一个执行完才返回。监听器内部的代码可以启动异步操作,但监听器本身是被同步调用的。

MaxListenersExceededWarning 是什么意思?

当同一个事件添加了超过 10 个监听器时,Node 会发出警告,因为这通常意味着每次请求都添加了一个监听器却从不移除。请移除多余的监听器;如果确实需要更多,就调用 emitter.setMaxListeners(n)。

emit 返回什么?

如果该事件至少有一个监听器就返回 true,没有任何监听器则返回 false。它不会返回监听器的返回值。

Node.js 中哪些对象是 EventEmitter?

Node 的大多数核心对象都是:HTTP 服务器、请求和响应、流、socket、process 以及 readline 接口。所以它们都有 on、once 和 off 方法。

Coddy 编程语言插图

和 Coddy 一起学编程

开始