Node-Event模塊簡介

Node的大量核心的API都是構建在Event模塊上面的,比如stream模塊,該模塊內置的data事件可在數據到來時觸發,一旦stream流對象爲可讀狀態則發送readable事件,當數據讀寫完畢後發送end事件等。發送事件其實並非stream本身實現,而是藉助於Event對象來實現事件的發送和監聽回調綁定。本節內容基於現有的Node v5.7版本,介紹了Node的事件模塊的主要API函數的使用,所有例子均可成功運行。

文章來自於:個人Blog jsmean

創建自己的事件模塊

下面我們通過繼承EventEmitter來實現自己己的事件對象,該對象監聽一個hello事件,當有事件到達的時候,觸發回調函數,並打印輸出傳遞的參數。

import EventEmitter from "events"
class MyEmitter extends EventEmitter{
    constructor(){
        super()
    }
}
const emitter1=new MyEmitter()
emitter1.on("hello",(username)=>{
    console.log("Welcome:"+username)
})
emitter1.emit("hello","Mike")

// Welcome:Mike

注:官方的例子中並未使用super函數去調用父類的構造函數,這樣的初始化將導致以下父類的構造函數中的內容得不到執行,無法將domain等對象綁定到新的自定義事件上,但是如果你不使用domain特性的話則不會產生任何影響.

function EventEmitter() {
  EventEmitter.init.call(this);
}

EventEmitter.init = function() {
  this.domain = null;
  if (EventEmitter.usingDomains) {
    // if there is an active domain, then attach to it.
    domain = domain || require('domain');
    if (domain.active && !(this instanceof domain.Domain)) {

      //綁定domain對象到函數對象中
      this.domain = domain.active;
    }
  }

  下面的初始化步驟,在addEventListener函數中做了判斷如果爲空則執行重新的初始化賦值
  if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
    this._events = {};
    this._eventsCount = 0;
  }

  this._maxListeners = this._maxListeners || undefined;
};

傳遞錯誤信息到回調函數

上述的例子中我們傳遞了一個參數名字到回調函數中,這裏我們可以同時傳遞多個參數,只要在回調函數中包含即可,並且可傳遞第一個參數爲:Error對象,供後續的回調函數來判斷。如果Error!=null則代表有錯誤發生。

import EventEmitter from "events"
class MyEmitter extends EventEmitter{
    constructor(){
        super()
    }
}
const env=process.env.NODE_ENV
const emitter1=new MyEmitter()

emitter1.on("Debug",(err,info)=>{
    if(err==null){
        console.log("Debug:"+info)
    }
})

if(env&&env=="production"){
    emitter1.emit("Debug",new Error("Debug"),null)  //useless   
}else{
    emitter1.emit("Debug",null,"this is debug info")    
}

上述例子中當我們使用 NODE_ENV=”production” node 2.js 來執行的時候,不會產生任何的信息,而直接node 2.js則會執行Debug信息輸出.

多樣化的監聽

once

node的核心event模塊在處理事件的時候,通過多個函數來支持不同的監聽方式 比如once,綁定一個事件,但是隻處理一次後就不再監聽。這裏我們看一下源碼,once的實現其實將我們自己傳遞的回調函數做了二次封裝,再綁定上封裝後的函數,封裝的函數首先執行了removeListener()移除了回調函數與事件的綁定,然後才執行的回調函數:

EventEmitter.prototype.once = function once(type, listener) {
  if (typeof listener !== 'function')
    throw new TypeError('"listener" argument must be a function');

  var fired = false;

  function g() {
    this.removeListener(type, g);

    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
  }

  g.listener = listener;
  this.on(type, g);

  return this;
};

setMaxListeners()

event模塊支持設置最大的監聽數目,默認情況下當監聽數目超過10個則產生一條warn信息,該最大值可通過函數setMaxListeners()來設置,當設置爲0的時候默認沒有限制。下面的例子中我們生成了20個監聽函數,並設置最大監聽數目爲10,當第11個監聽函數綁定上之後,產生了一個warn信息,並附帶了trace信息,但是程序仍舊執行了下去,並未暫停退出。

import EventEmitter from "events"
class MyEmitter extends EventEmitter{
    constructor(){
        super()
    }
}

const emitter1=new MyEmitter()

emitter1.setMaxListeners(10);

for(let i=0;i<20;i++){
   emitter1.on("hello",(username)=>{
      console.log("Welcome:"+username+"-"+i)
   })
}
emitter1.emit("hello","Mike")

//output

(node) warning: possible EventEmitter memory leak detected. 11 hello listeners added. Use emitter.setMaxListeners() to increase limit.
Trace
    at MyEmitter.addListener (events.js:252:17)
    at _loop (/Users/zhangmingkai/workshop/node/event/4.js:32:11)
    at Object.<anonymous> (/Users/zhangmingkai/workshop/node/event/4.js:38:2)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3
Welcome:Mike-0
Welcome:Mike-1
Welcome:Mike-2
...
Welcome:Mike-18
Welcome:Mike-19

addListener與removeListener

node的源碼寫的比較清晰,如下面代碼所示,我們可以看出on函數其實不過是一個addListener函數的別名而已,removeListener(事件名稱,回調函數)則執行清理操作,移除回調函數和綁定的事件。

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.addListener = function addListener(type, listener) {
  var m;
  var events;
  var existing;

  //判斷是否爲函數,如果類型不是函數類型則拋出異常
  if (typeof listener !== 'function')
    throw new TypeError('"listener" argument must be a function');

  // 獲得事件的列表
  events = this._events;
  if (!events) {
    //初始化事件對象和事件計數
    events = this._events = {};
    this._eventsCount = 0;
  } else {
    // To avoid recursion in the case that type === "newListener"! Before
    // adding it to the listeners, first emit "newListener".

    // 當有新的綁定產生的時候,發射一個newListener的事件
    if (events.newListener) {
      this.emit('newListener', type,
                listener.listener ? listener.listener : listener);

      // Re-assign `events` because a newListener handler could have caused the
      // this._events to be assigned to a new object
      events = this._events;
    }
    // 綁定同一個事件的回調函數列表
    existing = events[type];
  }

  if (!existing) {
    //第一次綁定的時候直接將函數賦值給existing變量
    existing = events[type] = listener;
    ++this._eventsCount;
  } else {
    if (typeof existing === 'function') {
      // 如果是第二次綁定則將原來的existing變量變爲一個函數數組
      existing = events[type] = [existing, listener];
    } else {
      // 第二次之後的綁定直接push即可
      existing.push(listener);
    }
    // 判斷是否超出了默認的或者設置的最大綁定數目   
    if (!existing.warned) {
      m = $getMaxListeners(this);
      if (m && m > 0 && existing.length > m) {
        existing.warned = true;
        if (!internalUtil)
          internalUtil = require('internal/util');

        internalUtil.error('warning: possible EventEmitter memory ' +
                           'leak detected. %d %s listeners added. ' +
                           'Use emitter.setMaxListeners() to increase limit.',
                           existing.length, type);
        console.trace();
      }
    }
  }
  //返回this則可執行級聯操作
  return this;
};

events模塊與監聽者設計模式

在設計模式中,監聽者模式意味着了一個主體,維護一個或者多個稱之爲監聽者的對象,並且當主體發生變化的時候通知這些監聽者。這樣監聽者就不用一直去詢問主體是否發生了變化。就像我們現實生活中去辦理一些業務的時候,工作人員一般會記錄一些我們的信息比如手機號,一旦他們處理完成後就聯繫我們,而不用我們隔一段時間就去查詢一下是否處理完畢了。

監聽者模式的最大好處是解耦了兩個對象之間的聯繫,甚至是兩個服務。比如我們使用Redis的訂閱發佈,或者RabbitMQ的訂閱發佈。數據的生產者和消費者可以是不同的服務,甚至是不同語言編寫的程序等。同樣在node的世界裏,events模塊已經幫我們實現了該模式,我們可以單獨使用events作爲中間件來處理事件的傳遞,也可以繼承實現自己的events來處理髮生與接收事件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章