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)這樣,參見以前的發佈訂閱管道化api
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捕獲到promise
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裏面的話就會出現下面的問題bash
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的處理。markdown
當事件被觸發時,若是沒有與該事件綁定的函數的話,該事件會被靜默忽略掉,可是若是事件的名稱是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/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