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);  // 1data.num++;
});
myEmitter.on('event', (data) => {console.log(data.num); // 2
});
myEmitter.emit('event', {num: 1
});

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

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

同步还是异步
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');// 输出结果
listener1
listener2
listener3
end

异常处理
由于监听函数的执行是同步执行的,所以针对同步的代码可以通过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');
// 输出结果
end
err

但是如果把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');// 输出结果
listener1
listener2
listener3
end
(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:199throw err;^Error [ERR_UNHANDLED_ERROR]: Unhandled error.at MyEmitter.emit (events.js:197:19)at Object.<anonymous> (/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 anywayprocess.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 anywayprocess.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');
// 输出结果
listener3
listener2
listener1
end

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 limitat _addListener (events.js:289:19)at MyEmitter.prependListener (events.js:313:14)at Object.<anonymous> (/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

Node.js 的 EventEmitter解读相关推荐

  1. 深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)

    events模块对外提供了一个 EventEmitter 对象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模块events中的类,用于对NodeJS中 ...

  2. WEB前端框架jQuery、VueJS、React.JS、Node.JS、Bootstrap解读

    Web前端框架可以分为两类: 1)JS的类库框架 JQuery.JS Angular.JS(模型,  scope作用域,controller,依赖注入,MVVM):前端MVC Vue.JS(MVVM) ...

  3. [原] 探索 EventEmitter 在 Node.js 中的实现

    你有没有想过,为什么浏览器的 div 上可以绑定多个 onclick 事件,点击一下 div 可以触发全部的事件,jquery 的 .on(),.off(),one() 又是如何实现的?Node.js ...

  4. 在Node.js中使用事件,监听器,定时器和回调

    Node.js通过其强大的事件驱动模型提供了可扩展性和性能,本篇文章的重点是理解该模型,以及它是如何不同于大部分Web服务器采用的传统线程模型的.了解事件模型至关重要,因为它可能迫使你改变设计应用程序 ...

  5. 深入浅出Node.js(上)

    (一):什么是Node.js Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟 ...

  6. 如何构建自定义 Node.js 事件发射器

    事件是具有软件或硬件意义的动作. 它们是由于用户活动(例如鼠标单击或击键)或直接来自系统(例如错误或通知)而发出的. JavaScript 语言使我们能够通过在事件处理程序中运行代码来响应事件. 由于 ...

  7. Node.js EventEmitter

    Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列. Node.js里面的许多对象都会分发事件:一个net.Server对象会在每次有新连接时分发一个事件, 一个fs.read ...

  8. 7、Node.js EventEmitter

    #######################################################################################介绍 Node.js Ev ...

  9. Node.js 2021年开发者报告解读

    大家好,我是若川.持续组织了5个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列& ...

最新文章

  1. UA OPTI501 电磁波 Lorentz Oscillator Model 1 Drude-Lorentz模型
  2. 计算机专业是否限制语种,高考日语选什么专业(如果高考选日语,大学选专业有什么限制)...
  3. 车主无忧:为什么放弃开源Kafka?
  4. 使用 ortp 发送原始 H.264 码流
  5. python3的面向对象_python3学习之面向对象
  6. git推送项目到码云(gitee)
  7. Memcached在大型网站中应用[php 转载]
  8. ASP.NET 实现PDF文件下载[转]
  9. JAVA怎么创建被继承的类_Java入门之类的继承
  10. 含泪整理最优质相机 单反 摄影3dm犀牛模型素材,你想要的这里都有
  11. (ensp)华为USG6000v防火墙双机热备份的配置
  12. 网红电商第一股首份财报继续亏损,如涵的网红效应还能持续多久?
  13. excel颠倒顺序从下到上排列的两种方法
  14. Trading Convexity for Scalability
  15. 图片降噪免费软件有什么?快把这些软件收好
  16. 无迹卡尔曼滤波器详解
  17. 手机当服务器(Termux)快速入门
  18. 2017年搜狗校招Java研发笔试编程题
  19. 如何删除mysql数据库的重复数据
  20. 如何练习打字/盲打(作者的感想与建议)

热门文章

  1. Linux中反引号(` `)、单引号(‘ ‘)、双引号(“ “)、花括号({ })的解释
  2. Duplicate keys detected: ‘0‘Duplicate keys detected: ‘1‘报错的原因及解决方法
  3. vue中slot,slot-scope,v-slot的用法和区别
  4. C++图像处理 -- 图像合成
  5. 小程序(十)小程序缓存
  6. 2022-2028年全球及中国羊皮避孕套行业发展现状调研及投资前景分析
  7. hibernate主键的生成器
  8. 【BZOJ4244】邮戳拉力赛 DP
  9. 千锋教育—Kafka—2021-08-27
  10. redis 使用 及 获取当前时间到今天截止的秒数