沐鳴註冊網站_Nodejs模塊之events

讀了 events 模塊的文檔,研究了幾個有意思的問題:

  • 事件驅動模型
  • 優雅的錯誤處理
  • 監聽器器隊列順序處理
  • 內存管理與防止泄漏

引用/轉載 請聲明出處:原文鏈接: xxoo521.com

事件驅動模型

Nodejs 使用了一個事件驅動、非阻塞 IO 的模型。events模塊是事件驅動的核心模塊。很多內置模塊都繼承了events.EventEmitter。

自己無需手動實現這種設計模式,直接繼承EventEmitter即可。代碼如下:

const { EventEmitter } = require("events");

class MyEmitter extends EventEmitter {}

const ins = new MyEmitter();
ins.on("test", () => {
    console.log("emit test event");
});
ins.emit("test");

優雅的錯誤處理

根據文檔,應該 EventEmitter 實例的error事件是個特殊事件。推薦做法是:在創建實例后,應該立即註冊error事件。

const ins = new MyEmitter();
ins.on("error", error => {
    console.log("error msg is", error.message);
});

註冊error事件后,我原本的理解是,所有事件回掉邏輯中的錯誤都會在 EventEmitter 內部被捕獲,並且在內部觸發 error 事件。

也就是說下面代碼,會打印:”error msg is a is not defined”。

ins.on("test", () => {
    console.log(a);
});

ins.emit("test");

然而,錯誤並沒有捕獲,直接拋出了異常。由此可見,EventEmitter 在執行內部邏輯的時候,並沒有try-catch。這個原因,請見Node Issue。簡單來講,Error 和 Exception 並不完全一樣。

如果按照正常想法,不想每一次都在外面套一層try-catch,那應該怎麼做呢?我的做法是在
EventEmitter 原型鏈上新增一個safeEmit函數。

EventEmitter.prototype.safeEmit = function(name, ...args) {
    try {
        return this.emit(name, ...args);
    } catch (error) {
        return this.emit("error", error);
    }
};

如此一來,運行前一段代碼的 Exception 就會被捕獲到,並且觸發error事件。前一段代碼的輸出就變成了:

error msg is a is not defined

監聽器隊列順序處理

對於同一個事件,觸發它的時候,函數的執行順序就是函數綁定時候的順序。官方庫提供了emitter.prependListener()和 emitter.prependOnceListener() 兩個接口,可以讓新的監聽器直接添加到隊列頭部。

但是如果想讓新的監聽器放入任何監聽器隊列的任何位置呢?在原型鏈上封裝了 insertListener 方法。

EventEmitter.prototype.insertListener = function(
    name,
    index,
    callback,
    once = false
) {
    // 如果是once監聽器,其數據結構是 {listener: Function}
    // 正常監聽器,直接是 Function
    const listeners = ins.rawListeners(name);
    const that = this;
    // 下標不合法
    if (index > listeners.length || index < 0) {
        return false;
    }
    // 綁定監聽器數量已達上限
    if (listeners.length >= this.getMaxListeners()) {
        return false;
    }
    listeners.splice(index, 0, once ? { listener: callback } : callback);
    this.removeAllListeners(name);
    listeners.forEach(function(item) {
        if (typeof item === "function") {
            that.on(name, item);
        } else {
            const { listener } = item;
            that.once(name, listener);
        }
    });
    return true;
};

使用起來,效果如下:

const ins = new MyEmitter();
ins.on("error", error => {
    console.log("error msg is", error.message);
});

ins.on("test", () => {
    console.log("test 1");
});

ins.on("test", () => {
    console.log("test 2");
});

// 監聽器隊列中插入新的監聽器,一個是once類型,一個不是once類型
ins.insertListener(
    "test",
    0,
    () => {
        console.log("once test insert");
    },
    true
);
ins.insertListener("test", 1, () => {
    console.log("test insert");
});

連續調用兩次ins.emit(“test”),結果輸出如下:

# 第一次
once test insert
test insert
test 1
test 2
# 第二次: once 類型的監聽器調用一次后銷毀
test insert
test 1
test 2

內存管理與防止泄漏

在綁定事件監聽器的時候,如果監聽器沒有被 remove,那麼存在內存泄漏的風險。

我知道的常見做法如下:

  • 經常 CR,移除不需要的事件監聽器
  • 通過once綁定監聽器,調用一次后,監聽器被自動移除
  • [推薦]hack 一個更安全的EventEmitter

參考鏈接

  • NodeJS Issue
  • Docs: events

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/7232