Node中的Events模塊介紹及應用

Node 中的 Events

Node 的 Events 模塊隻定義瞭一個類,就是 EventEmitter(以下簡稱 Event ),這個類在很多 Node 本身以及第三方模塊中大量使用,通常是用作基類被繼承。

在 Node 中,事件的應用遍及代碼的每一個角落。

1. 事件和監聽器

Node 程序中的對象會產生一系列的事件,它們被稱為事件觸發器(event emitter),例如一個 HTTP Server 會在每次有新連接時觸發一個事件,一個 Readable Stream 會在文件打開時觸發一個事件等。

所有能觸發事件的對象都是 EventEmitter 類的實例。EventEmitter 定義瞭 on 方法,該方法的聲明如下:

emitter.on(eventName, listener)
eventName <String> | <Symbol> The name of the event.
listener <Function> The callback function

on 方法接受兩個參數:需要監聽的事件的名稱,當事件觸發時需要調用的函數。因為 EventEmitter 是接口,從 EventEmitter 繼承的類需要使用 new 關鍵字來構造。

觸發事件監聽器很簡單,隻要調用 EventEmitter實例的 emit 方法就行瞭。需要註意的是,這些事件是針對某個實例的,不存在全局的事件。當你調用 on 方法的時候,需要綁定在特定的基於 EventEmitter 的對象上。EventEmitter 類不同的實例之間也不會共享事件。

下面是一個事件註冊和觸發事件的例子。

const eventEmitter = require('events');
const myEmitter = new eventEmitter();
myEmitter.on('begin', () => {
  console.log('begin');
});
myEmitter.emit('begin');

上面的代碼中,首先初始化瞭一個 EventEmitter 實例,然後註冊瞭一個名為 begin 的事件,之後調用 emit 方法觸發瞭這一事件。

用戶可以註冊多個同名的事件,在上面的例子中,如果註冊兩個名為 begin 的事件,那麼它們都會被觸發。

如果想獲取當前的 emitter 一共註冊瞭哪些事件,可以使用 eventNames 方法。

console.log(myEmitter.eventNames());

該方法會輸出包括全部事件名稱的數組。就算註冊瞭兩個同名的 event,輸出結果也隻有一個,說明該方法的結果集並不包含重復結果。

2. 處理 error 事件

由於 Node 代碼運行在單線程環境中,那麼運行時出現的任何錯誤都有可能導致整個進程退出。利用事件機制可以實現簡單的錯誤處理功能。

當 Node 程序出現錯誤的時候,通常會觸發一個錯誤事件,如果代碼中沒有註冊相應的處理方法,會導致 Node 進程崩潰退出。例如:

myEmitter.emit("error", new Error("crash!"));

上面的代碼主動拋出瞭一個 emor,相當於:

throw new Error ("crash");

如果我們不想因為拋出一個 error 而使進程退出,那麼可以讓 uncaughtException 事件作為最後一道防線來捕獲異常。

const eventEmitter = require('events');
const myEmitter = new eventEmitter();
process.on('uncaughtException', () => {
  console.log('got error');
});
throw new Error('Error occurred');

這種錯誤處理的方式雖然可以捕獲異常,避免瞭進程的退出,但不值得提倡。

關於其常見的方法如下:

  • emitter.addListener/on(eventName, listener) :添加類型為 eventName 的監聽事件到事件數組尾部
  • emitter.prependListener(eventName, listener):添加類型為 eventName 的監聽事件到事件數組頭部
  • emitter.emit(eventName[, ...args]):觸發類型為 eventName 的監聽事件
  • emitter.removeListener/off(eventName, listener):移除類型為 eventName 的監聽事件
  • emitter.once(eventName, listener):添加類型為 eventName 的監聽事件,以後隻能執行一次並刪除
  • emitter.removeAllListeners([eventName]): 移除全部類型為 eventName 的監聽事件

3. 繼承 Events 模塊

在實際的開發中,通常不會直接使用 Event 模塊來進行事件處理,而是選擇將其作為基類進行繼承的方式來使用 Event,在 Node 的內部實現中,凡是提供瞭事件機制的模塊,都會在內部繼承 Event 模塊。

4. 手寫 EventEmitter

下面我們來看看如何手寫一個 EventEmitter

class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(type, handler) {
    if (!this.events[type]) {
      this.events[type] = [];
    }
    this.events[type].push(handler);
  }
  addListener(type, handler) {
    this.on(type, handler)
  }
  prependListener(type, handler) {
    if (!this.events[type]) {
      this.events[type] = [];
    }
    this.events[type].unshift(handler);
  }
  removeListener(type, handler) {
    if (!this.events[type]) {
      return;
    }
    this.events[type] = this.events[type].filter(item => item !== handler);
  }
  off(type, handler) {
    this.removeListener(type, handler)
  }
  emit(type, ...args) {
    this.events[type].forEach((item) => {
      Reflect.apply(item, this, args);
    });
  }
  once(type, handler) {
    this.on(type, this._onceWrap(type, handler, this));
  }
  _onceWrap(type, handler, target) {
    const state = {
      fired: false,
      handler,
      type,
      target
    };
    const wrapFn = this._onceWrapper.bind(state);
    state.wrapFn = wrapFn;
    return wrapFn;
  }
  _onceWrapper(...args) {
    if (!this.fired) {
      this.fired = true;
      Reflect.apply(this.handler, this.target, args);
      this.target.off(this.type, this.wrapFn);
    }
  }
}

到此這篇關於Node中的Events事件介紹及應用的文章就介紹到這瞭,更多相關Node Events內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: