讀了
events
子產品的文檔,研究了幾個有意思的問題:
- ?️ 事件驅動模型
- ?️ 優雅的錯誤處理
- ?️ 監聽器器隊列順序處理
- ?️ 記憶體管理與防止洩漏
- ? 配合 Promise 使用
引用/轉載 請聲明出處:原文連結: 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