NodeJS Events 必知必會

1. 環境

  • node 8.11.3

2. 基本使用

// 01.js

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
myEmitter.emit('event');

輸出:node

an event occurred!

3. 傳參與this指向

  • emit()方法能夠傳不限制數量的參數。
  • 除了箭頭函數外,在回調函數內部,this會被綁定到EventEmitter類的實例上
// 02.js
const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

myEmitter.on('event', function (a, b){
  console.log(a, b, this, this === myEmitter)
})

myEmitter.on('event', (a, b) => {
  console.log(a, b, this, this === myEmitter)
})

myEmitter.emit('event', 'a', {name:'wdd'})

輸出:git

a { name: 'wdd' } MyEmitter {
  domain: null,
  _events: { event: [ [Function], [Function] ] },
  _eventsCount: 1,
  _maxListeners: undefined } true
a { name: 'wdd' } {} false

4. 同步仍是異步調用listeners?

  • emit()法會同步按照事件註冊的順序執行回調
// 03.js
const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

myEmitter.on('event', () => {
  console.log('01 an event occurred!')
})

myEmitter.on('event', () => {
  console.log('02 an event occurred!')
})

console.log(1)
myEmitter.emit('event')
console.log(2)

輸出:github

1
01 an event occurred!
02 an event occurred!
2

深刻思考,爲何事件回調要同步?異步了會有什麼問題?bootstrap

同步去調用事件監聽者,可以確保按照註冊順序去調用事件監聽者,而且避免競態條件和邏輯錯誤。dom

5. 如何只訂閱一次事件?

  • 使用once去只訂閱一次事件
// 04.js
const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()

let m = 0
myEmitter.once('event', () => {
  console.log(++m)
})
myEmitter.emit('event')
myEmitter.emit('event')

6. 不訂閱,就發飆的錯誤事件

error是一個特別的事件名,當這個事件被觸發時,若是沒有對應的事件監聽者,則會致使程序崩潰。異步

events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: test
    at Object.<anonymous> (/Users/xxx/github/node-note/events/05.js:12:25)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

因此,最好老是給EventEmitter實例添加一個error的監聽器函數

const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

myEmitter.on('error', (err) => {
  console.log(err)
})

console.log(1)
myEmitter.emit('error', new Error('test'))
console.log(2)

7. 內部事件 newListener與removeListener

newListener與removeListener是EventEmitter實例的自帶的事件,你最好不要使用一樣的名字做爲自定義的事件名。ui

  • newListener在訂閱者被加入到訂閱列表前觸發
  • removeListener在訂閱者被移除訂閱列表後觸發
// 06.js 
const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

myEmitter.on('newListener', (event, listener) => {
  console.log('----')
  console.log(event)
  console.log(listener)
})

myEmitter.on('myEmitter', (err) => {
  console.log(err)
})

輸出:this

從輸出能夠看出,即便沒有去觸發myEmitter事件,on()方法也會觸發newListener事件。code

----
myEmitter
[Function]

8. 事件監聽數量限制

  • myEmitter.listenerCount('event'): 用來計算一個實例上某個事件的監聽者數量
  • EventEmitter.defaultMaxListeners: EventEmitter類默認的最大監聽者的數量,默認是10。超過會有警告輸出。
  • myEmitter.getMaxListeners(): EventEmitter實例默認的某個事件最大監聽者的數量,默認是10。超過會有警告輸出。
  • myEmitter.eventNames(): 返回一個實例上又多少種事件

EventEmitter和EventEmitter實例的最大監聽數量爲10並非一個硬性規定,只是一個推薦值,該值能夠經過setMaxListeners()接口去改變。

  • 改變EventEmitter的最大監聽數量會影響到全部EventEmitter實例
  • 該變EventEmitter實例的最大監聽數量只會影響到實例自身

如無必要,最好的不要去改變默認的監聽數量限制。事件監聽數量是node檢測內存泄露的一個標準一個維度。

EventEmitter實例的最大監聽數量不是一個實例的全部監聽數量。

例如同一個實例A類型事件5個監聽者,B類型事件6個監聽者,這個並不會有告警。若是A類型有11個監聽者,就會有告警提示。

若是在事件中發現相似的告警提示Possible EventEmitter memory leak detected,要知道從事件最大監聽數的角度去排查問題。

// 07.js
const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

const maxListeners = 11

for (let i = 0; i < maxListeners; i++) {
  myEmitter.on('event', (err) => {
    console.log(err, 1)
  })
}

myEmitter.on('event1', (err) => {
  console.log(err, 11)
})

console.log(myEmitter.listenerCount('event'))
console.log(EventEmitter.defaultMaxListeners)
console.log(myEmitter.getMaxListeners())
console.log(myEmitter.eventNames())

輸出:

11
10
10
[ 'event', 'event1' ]
(node:23957) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit
相關文章
相關標籤/搜索