博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Node.js EventEmitter解读
阅读量:6417 次
发布时间:2019-06-23

本文共 9224 字,大约阅读时间需要 30 分钟。

Node.js核心API基于异步事件驱动的架构,fs.ReadStream可以通过on()方式来监听事件其实都是由于继承了EventEmitter类,如下所示

const fs = require('fs');const EventEmitter = require('events');var stream = fs.createReadStream('./a.js');console.log(stream instanceof EventEmitter);   // true复制代码

除了流之外,net.Server,以及process也都是继承自EventEmitter所以可以监听事件。

const EventEmitter = require('events');const net = require('net');var server = net.createServer(function(client) {  console.log(client instanceof EventEmitter);   // true});server.listen(8000, () => {  console.log('server started on port 8000');});console.log(process instanceof EventEmitter); // true复制代码

on监听的事件的名称可以包含特殊字符(比如'$'、'*’、'~'都是可以的),但是需要注意是大小写敏感的。

const EventEmitter = require('events');class MyEmitter extends EventEmitter {}const myEmitter = new MyEmitter();myEmitter.on('*$~', () => {  console.log('an event occurred!');});myEmitter.emit('*$~');复制代码

当EventEmitter对象发出一个事件的时候,所有与此事件绑定的函数都会被同步调用。绑定的函数调用的返回值都会被忽略掉(这一点会带来其他问题,后面会提到)。但是如果是对象被修改的话,是可以传递到其他监听函数的,比如:

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', function(data) {  console.log(data.num);  // 1  data.num++;});myEmitter.on('event', (data) => {  console.log(data.num); // 2});myEmitter.emit('event', {  num: 1});复制代码

这个是JS关于引用类型的特性,与EventEmitter一点关系也没有,实际情况下不推荐这种写法,因为可维护性比较低。

如果是自己实现类似EventEmitter机制的话,是可以做到监听函数之间的执行结果互相传递的(比如类似a.pipe(b).pipe(c)这样,参见之前的

同步还是异步

EventEmitter触发事件的时候,各监听函数的调用是同步的(注意'end'的输出在最后),但是并不是说监听函数里不能包含异步的代码(比如下面的listener2就是一个异步的)

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', function() {  console.log('listener1');});myEmitter.on('event', async function() {  console.log('listener2');  await new Promise((resolve, reject) => {    setTimeout(() => {      resolve(1);    }, 1000);  });});myEmitter.on('event', function() {  console.log('listener3');});myEmitter.emit('event');console.log('end');// 输出结果listener1listener2listener3end复制代码

异常处理

由于监听函数的执行是同步执行的,所以针对同步的代码可以通过try catch捕获到

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', function() {  a.b();  console.log('listener1');});myEmitter.on('event', async function() {  console.log('listener2');  await new Promise((resolve, reject) => {    setTimeout(() => {      resolve(1);    }, 1000);  });});myEmitter.on('event', function() {  console.log('listener3');});try {  myEmitter.emit('event');} catch(e) {  console.error('err');}console.log('end');// 输出结果enderr复制代码

但是如果把a.b();移到第二个listener里面的话就会出现下面的问题

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', function() {  console.log('listener1');});myEmitter.on('event', async function() {  console.log('listener2');  a.b();  await new Promise((resolve, reject) => {    setTimeout(() => {      resolve(1);    }, 1000);  });});myEmitter.on('event', function() {  console.log('listener3');});try {  myEmitter.emit('event');} catch(e) {  console.error('err');}console.log('end');// 输出结果listener1listener2listener3end(node:9046) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): ReferenceError: a is not defined(node:9046) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code复制代码

async函数的特点就在于它的返回值是一个Promise,如果函数体内出现错误的话Promise就是reject状态。Node.js不推荐忽略reject的promise,而EventEmitter对于各监听函数的返回值是忽略的,所以才会出现上面的情况。明白了问题的原因后我们就可以确定对于上面的情况的话,需要在第二个listener里面增加try catch的处理。

当事件被触发时,如果没有与该事件绑定的函数的话,该事件会被静默忽略掉,但是如果事件的名称是error的话,没有与此相关的事件处理的话,程序就会crash退出

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', function(data) {  console.log(data);});myEmitter.emit('error');events.js:199      throw err;      ^Error [ERR_UNHANDLED_ERROR]: Unhandled error.    at MyEmitter.emit (events.js:197:19)    at Object.
(/Users/xiji/workspace/learn/event-emitter/b.js:7:11) at Module._compile (module.js:641:30) at Object.Module._extensions..js (module.js:652:10) at Module.load (module.js:560:32) at tryModuleLoad (module.js:503:12) at Function.Module._load (module.js:495:3) at Function.Module.runMain (module.js:682:10) at startup (bootstrap_node.js:191:16) at bootstrap_node.js:613:3复制代码

只有添加了针对error事件的处理函数的话程序才不会退出了。

另外一种方式是process监听uncaughtException事件,但是这并不是推荐的做法,因为uncaughtException事件是非常严重的,通常情况下在uncaughtException的处理函数里面一般是做一些上报或者清理工作,然后执行process.exit(1)让程序退出了。

process.on('uncaughtException', function(err) {    console.error('uncaught exception:', err.stack || err);  // orderly close server, resources, etc.  closeEverything(function(err) {    if (err)      console.error('Error while closing everything:', err.stack || err);    // exit anyway    process.exit(1);  });});复制代码

监听一次

如果在同一时刻出现了多次uncaught exception的话,那么closeEverything就可能会被触发多次,这又有可能会带来新的问题。因此推荐的做法是只对第一次的uncaught excepition做监听处理,这种情况下就需要用到once方法了

process.once('uncaughtException', function(err) {    // orderly close server, resources, etc.  closeEverything(function(err) {    if (err)      console.error('Error while closing everything:', err.stack || err);    // exit anyway    process.exit(1);  });});复制代码

按照上面的写法就不会出现closeEverything被触发两次的现象了,不过对于第二次的uncaughtException因为没有相应的处理函数,会导致程序立即退出,为了解决这个问题,我们可以在once之外,再增加每次异常的错误记录,如下所示:

process.on('uncaughtException', function(err) {    console.error('uncaught exception:', err.stack || err);});复制代码

监听函数的执行顺序

之前的例子(on(eventName, listener))可以看到各监听函数的执行顺序与代码的抒写顺序一致,EventEmitter还提供了其他的方法可以调整监听函数的执行顺序,虽然并不如那样灵活。

除了on的方式(向后追加),我们还可以使用prependListener的方法来(向前插入)增加监听函数

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.prependListener('event', function() {  console.log('listener1');});myEmitter.prependListener('event', async function() {  console.log('listener2');});myEmitter.prependListener('event', function() {  console.log('listener3');});myEmitter.emit('event');console.log('end');// 输出结果listener3listener2listener1end复制代码

EventEmiter在每次有新的listener加入之前都会触发一个'newListener'的事件,所以可以也可以通过监听这个事件来实现向前插入监听函数,但是需要注意的一点是为了避免无限循环的出现,如果在newListener的监听函数里有增加监听函数的代码的话,那么对于newListener的监听应该使用once方式。

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.once('newListener', (event, listener) => {  if (event === 'event') {    myEmitter.on('event', () => {      console.log('B');    });  }});myEmitter.on('event', () => {  console.log('A');});myEmitter.emit('event');// 输出结果//   B//   A复制代码

调整默认最大listener

默认情况下针对单一事件的最大listener数量是10,如果超过10个的话listener还是会执行,只是控制台会有警告信息,告警信息里面已经提示了操作建议,可以通过调用emitter.setMaxListeners()来调整最大listener的限制

(node:9379) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit复制代码

上面的警告信息的粒度不够,并不能告诉我们是哪里的代码出了问题,可以通过process.on('warning')来获得更具体的信息(emitter、event、eventCount)

process.on('warning', (e) => {  console.log(e);}){ MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit    at _addListener (events.js:289:19)    at MyEmitter.prependListener (events.js:313:14)    at Object.
(/Users/xiji/workspace/learn/event-emitter/b.js:34:11) at Module._compile (module.js:641:30) at Object.Module._extensions..js (module.js:652:10) at Module.load (module.js:560:32) at tryModuleLoad (module.js:503:12) at Function.Module._load (module.js:495:3) at Function.Module.runMain (module.js:682:10) at startup (bootstrap_node.js:191:16) name: 'MaxListenersExceededWarning', emitter: MyEmitter { domain: null, _events: { event: [Array] }, _eventsCount: 1, _maxListeners: undefined }, type: 'event', count: 11 }复制代码

this指向

监听函数如果采用如下写法的话,那么this的指向就是事件的emitter

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', function(a, b) {  console.log(a, b, this === myEmitter); // a b true});myEmitter.emit('event', 'a', 'b');复制代码

如果是用箭头函数写法的话,那么this就不是指向emitter了

const EventEmitter = require('events');class MyEmitter extends EventEmitter{};const myEmitter = new MyEmitter();myEmitter.on('event', (a, b) => {  console.log(a, b, this === myEmitter); // a b false});myEmitter.emit('event', 'a', 'b');复制代码

其他

emitter.off(eventName, listener) 、emitter.removeListener(eventName, listener)、emitter.removeAllListeners([eventName])可以移除监听。函数的返回值是emitter对象,因此可以使用链式语法

emitter.listenerCount(eventName)可以获取事件注册的listener个数

emitter.listeners(eventName)可以获取事件注册的listener数组副本。

参考资料

https://netbasal.com/javascript-the-magic-behind-event-emitter-cce3abcbcef9

https://medium.com/technoetics/node-js-event-emitter-explained-d4f7fd141a1a

https://medium.com/yld-engineering-blog/using-an-event-emitter-common-use-and-edge-cases-b5eb518a4bd2

https://medium.freecodecamp.org/understanding-node-js-event-driven-architecture-223292fcbc2d

https://nodejs.org/api/events.html

转载地址:http://nlpra.baihongyu.com/

你可能感兴趣的文章
test for windows live writer plugins
查看>>
Tiny210 U-BOOT(二)----配置时钟频率基本原理
查看>>
Java中字符串相等与大小比较
查看>>
文字超过字符长度 显示… 点点点 jquery
查看>>
读javascript高级程序设计14-错误处理与调试
查看>>
代理模式
查看>>
javaweb学习总结(二十四)——jsp传统标签开发
查看>>
让script的type属性等于text/html
查看>>
[Docker] Docker Machine intro
查看>>
HA 高可用软件系统保养指南
查看>>
linux 文件系统sysvinit 流程分析
查看>>
体素科技:2018年,算法驱动下的医学影像分析进展
查看>>
Vue 折腾记 - (8) 写一个挺靠谱的多地区选择组件
查看>>
VS Code折腾记 - (3) 多图解VSCode基础功能
查看>>
再不懂区块链,你就OUT了!
查看>>
教你玩转自定义View—手撸一个倒计时控件如此简单
查看>>
『翻译』Node.js 调试
查看>>
我的iOS开发之路总结(更新啦~)
查看>>
Java NIO之拥抱Path和Files
查看>>
微信原图泄露的只能是 Exif ,你的隐私不在这!!!
查看>>