Node.js核心API基于异步事件驱动架构,fs.ReadStream可以通过on()监听事件,因为它继承了EventEmitter类,如下所示constfs=require('fs');constEventEmitter=require('事件');varstream=fs.createReadStream('./a.js');console.log(流实例EventEmitter);//trueexceptstream,net.Server和process也继承自EventEmitter,因此它们可以监听事件。constEventEmitter=require('事件');constnet=require('net');varserver=net.createServer(function(client){console.log(clientinstanceofEventEmitter);//true});server.listen(8000,()=>{console.log('serverstartedonport8000');});console.log(处理实例的EventEmitter);//trueon监听的事件名称可以包含特殊字符(比如'$','*','~'都可以),但是要注意区分大小写。constEventEmitter=require('events');classMyEmitterextendsEventEmitter{}constmyEmitter=newMyEmitter();myEmitter.on('*$~',()=>{console.log('事件发生!');});myEmitter.emit('*$~');当EventEmitter对象发出一个事件时,绑定到该事件的所有函数将被同步调用。绑定函数调用的返回值将被忽略(这会导致其他问题,稍后会提到)。但是如果修改了对象,可以传递给其他的监听函数,例如:constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.on('event',function(data){console.log(data.num);//1data.num++;});myEmitter.on('event',(data)=>{console.log(data.num);//2});myEmitter.emit('事件',{num:1});这是JS关于引用类型的特性,与EventEmitter无关。实践中不推荐这种写法,因为可维护性比较低。如果自己实现一个类似的EventEmitter机制,就可以在监听函数之间互相传递执行结果(比如像a.pipe(b).pipe(c),看前面的发布订阅管道是同步的还是异步的EventEmitter当一个事件被触发时,各个监听函数的调用是同步的(注意'end'的输出是在最后),但并不代表监听函数不能包含异步代码(例如下面的listener2是一个异步的)constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.on('event',function(){console.log('listener1');});myEmitter.on('event',asyncfunction(){console.log('listener2');awaitnewPromise((resolve,reject)=>{setTimeout(()=>{resolve(1);},1000);});});myEmitter.on('event',function(){console.log('listener3');});myEmitter.emit('event');console.log('end');//输出结果listener1listener2listener3end异常处理由于监听函数的执行是同步执行的,同步的代码可以通过trycatchconstEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.on('事件',function(){a.b();console.log('listener1');});myEmitter.on('event',asyncfunction(){console.log('听众2');awaitnewPromise((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');//输出结果enderrbutifa.b();移到第二个监听器,会出现下面的问题constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.on('event',function(){console.log('listener1');});myEmitter.on('event',asyncfunction(){console.log('listener2');a.b();awaitnewPromise((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:UnhandledPromiserejection(rejectionid:1):ReferenceError:a未定义(节点:9046)[DEP0018]DeprecationWarning:未处理的承诺拒绝已弃用。以后,未处理的promise拒绝将以非零退出终止Node.js进程codeasync函数的特点是它的返回值是一个Promise。如果函数体出现错误,则Promise处于拒绝状态。Node.js不推荐忽略拒绝的promise,而EventEmitter忽略了各个监听函数的返回值,所以出现了上面的情况。了解问题原因后,我们可以确定,对于上述情况,需要在第二个监听器中添加trycatch处理。当一个事件被触发时,如果没有绑定事件的函数,该事件会被静默忽略,但如果事件名称错误,如果没有相关的事件处理,程序会崩溃并退出constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.on('event',function(data){console.log(data);});myEmitter.emit('错误');events.js:199抛出错误;^错误[ERR_UNHANDLED_ERROR]:未处理的错误。在MyEmitter.emit(events.js:197:19)在Object.(/Users/xiji/workspace/learn/event-emitter/b.js:7:11)在Module._compile(module.js:641:30)在Object.Module._extensions..js(module.js:652:10)在Module.load(module.js:560:32)在tryModuleLoad(module.js:503:12)在Function.Module._load(module.js:495:3)atFunction.Module.runMain(module.js:682:10)atstartup(bootstrap_node.js:191:16)atbootstrap_node.js:613:3只有处理程序添加了错误事件,程序不会退出。另一种方式是通过进程监听uncaughtException事件,但不推荐这样做,因为uncaughtException事件非常严重。通常,在uncaughtException处理函数中,通常会做一些报告或清理工作,然后执行process.exit(1)让程序退出。process.on('uncaughtException',function(err){console.error('uncaughtexception:',err.stack||err);//有序关闭服务器、资源等closeEverything(function(err){if(err)console.error('Errorwhileclosingeverything:',err.stack||err);//无论如何退出process.exit(1);});});如果同时有多个未捕获的异常,监听一次,那么closeEverything可能会触发多次,可能会引发新的问题。因此,推荐的方法是只监听并处理第一个未捕获的异常。在这种情况下,就需要once方法。process.once('uncaughtException',function(err){//按顺序关闭服务器、资源等.closeEverything(function(err){if(err)console.error('Errorwhileclosingeverything:',err.stack||err);//无论如何退出process.exit(1);});});按照上面的写法不会导致closeEverything被触发两次,但是对于第二次uncaughtException因为没有对应的handler函数,程序会立即退出。为了解决这个问题,我们可以添加每个异常的错误记录如下:;});监听函数的执行顺序在前面的例子(on(eventName,listener))中,我们可以看到每个监听函数的执行顺序和代码写的顺序是一致的。EventEmitter还提供了其他方法来调整监听函数的执行顺序,虽然不如发布-订阅管道灵活。除了on方法(向后追加)之外,我们还可以使用prependListener方法(向后追加)增加监听函数constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.prependListener('event',function(){console.log('listener1');});myEmitter.prependListener('event',asyncfunction(){console.log('listener2');});myEmitter.prependListener('event',function(){console.log('listener3');});myEmitter.emit('event');console.log('end');//每次输出结果listener3listener2listener1endEventEmiter在添加新的监听器之前,会触发一个'newListener'事件,所以也可以通过监听这个事件向前插入监听函数,但是需要注意的是,为了避免死循环,如果监听函数的newListener如果有增加监听功能的代码,那么newListener的监听应该使用once方法。constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.once('newListener',(event,listener)=>{if(event==='事件'){myEmitter.on('event',()=>{console.log('B');});}});myEmitter.on('event',()=>{console.log('A');});myEmitter.emit('event');//输出结果//B//A调整默认最大监听。默认情况下,单个事件最大监听数为10个,如果超过10个,监听器仍然会执行,但是控制台会有一个警告信息,里面有建议的操作建议,可以调整通过调用emitter.setMaxListeners()(节点:9379)MaxListenersExceededWarning限制最大侦听器:检测到可能的EventEmitter内存泄漏。增加了11个事件监听器。使用emitter.setMaxListeners()增加limit上面警告信息的粒度不够,不能告诉我们代码哪里错了。您可以通过process.on('warning')(emitter,event,eventCount)process.on('warning',(e)=>{console.log(e);}){MaxListenersExceededWarning:PossibleEventEmitter获取更具体的信息检测到内存泄漏。添加了11个事件侦听器。使用emitter.setMaxListeners()增加_a处的限制ddListener(events.js:289:19)在MyEmitter.prependListener(events.js:313:14)在Object.(/Users/xiji/workspace/learn/event-emitter/b.js:34:11)在Module._compile(module.js:641:30)在Object.Module._extensions..js(module.js:652:10)在Module.load(module.js:560:32)在tryModuleLoad(module.js:503:12)在Function.Module._load(module.js:495:3)在Function.Module.runMain(module.js:682:10)在启动时(bootstrap_node.js:191:16)name:'MaxListenersExceededWarning',emitter:MyEmitter{domain:null,_events:{event:[Array]},_eventsCount:1,_maxListeners:undefined},type:'event',count:11}this指向监听函数如果写成如下,那么这个点就是事件的发射器constEventEmitter=require('events');classMyEmitterextendsEventEmitter{};constmyEmitter=newMyEmitter();myEmitter.on('event',function(a,b){console.log(a,b,this===myEmitter);//abtrue});myEmitter.emit('事件','a','b');如果是用箭头函数写的如果是这样,那么这并不指向发射器>{console.log(a,b,this===myEmitter);//abfalse});myEmitter.emit('事件','a','b');其他emitter.off(eventName,listener),emitter.removeListener(eventName,listener),emitter.removeAllListeners([eventName])可以移除监听器。该函数的返回值是一个发射器对象,因此您可以使用链式语法emitter.listenerCount(eventName)来获取为事件发射器注册的侦听器的数量。listeners(eventName)可以获得为事件注册的侦听器数组的副本。参考资料https://netbasal.com/javascri...https://medium.com/technoetic...https://medium.com/yld-engine...https://medium.freecodecamp.o。..https://nodejs.org/api/events…