Node.js核心API基于异步事件驱动的架构,fs.ReadStream能够经过on()方式来监听事件其实都是因为继承了EventEmitter类,以下所示javascript
const fs = require('fs'); const EventEmitter = require('events'); var stream = fs.createReadStream('./a.js'); console.log(stream instanceof EventEmitter); // true
除了流以外,net.Server,以及process也都是继承自EventEmitter因此能够监听事件。html
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监听的事件的名称能够包含特殊字符(好比'$'、'*’、'~'都是能够的),可是须要注意是大小写敏感的。java
const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('*$~', () => { console.log('an event occurred!'); }); myEmitter.emit('*$~');
当EventEmitter对象发出一个事件的时候,全部与此事件绑定的函数都会被同步调用。绑定的函数调用的返回值都会被忽略掉(这一点会带来其余问题,后面会提到)。可是若是是对象被修改的话,是能够传递到其余监听函数的,好比:node
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一点关系也没有,实际状况下不推荐这种写法,由于可维护性比较低。bootstrap
若是是本身实现相似EventEmitter机制的话,是能够作到监听函数之间的执行结果互相传递的(好比相似a.pipe(b).pipe(c)这样,参见以前的 发布订阅管道化
EventEmitter触发事件的时候,各监听函数的调用是同步的(注意'end'的输出在最后),可是并非说监听函数里不能包含异步的代码(好比下面的listener2就是一个异步的)segmentfault
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捕获到api
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的处理。promise
当事件被触发时,若是没有与该事件绑定的函数的话,该事件会被静默忽略掉,可是若是事件的名称是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.<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 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'); // 输出结果 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数量是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.<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的指向就是事件的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/javascri...
https://medium.com/technoetic...
https://medium.com/yld-engine...