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 方法。