Event-driven model
Nodejs uses an event-driven, non-blocking IO model. events
Event-driven module is the core module. Many built-in modules are inherited events.EventEmitter
.
Own without having to manually implement this design pattern, the direct successor EventEmitter
to. code show as below:
const { EventEmitter } = require("events");
class MyEmitter extends EventEmitter {}
const ins = new MyEmitter();
ins.on("test", () => {
console.log("emit test event");
});
ins.emit("test");
Elegant error handling
According to the document, should EventEmitter instance of error
the event is a special event. Recommended practice : after instance creation, should be registered immediately error
event.
const ins = new MyEmitter();
ins.on("error", error => {
console.log("error msg is", error.message);
});
Registration error
after the event, I had the understanding that all events back out logic errors are caught inside EventEmitter, and trigger inside the error
event.
That the following code will print: "error msg is a is not defined".
ins.on("test", () => {
console.log(a);
});
ins.emit("test");
However, the error did not capture, direct throw an exception. Thus, EventEmitter in the implementation of internal logic, and no try-catch
. For this reason, see the Node Issue . Simply put, Error and Exception not exactly the same.
If the normal idea, do not want every time one sets out try-catch
how it should do it? My practice is to
add a prototype on EventEmitter chain safeEmit
function.
EventEmitter.prototype.safeEmit = function(name, ...args) {
try {
return this.emit(name, ...args);
} catch (error) {
return this.emit("error", error);
}
};
Thus, Exception piece of code will run before being captured, and trigger error
events. Output of the previous piece of code becomes:
error msg is a is not defined
Queue Listener sequential processing
For the same event, when it is triggered, the execution order function is a function of the time of the binding order. Library provides official emitter.prependListener()
and emitter.prependOnceListener()
two interfaces, allowing new listeners added directly to the head of the queue.
But if you want the new listener into any position in any listener queue it? A chain on a prototype packaging insertListener
method.
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;
};
Use, the effect is as follows:
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");
});
Two successive calls ins.emit("test")
, the resulting output is as follows:
# 第一次
once test insert
test insert
test 1
test 2
# 第二次: once 类型的监听器调用一次后销毁
test insert
test 1
test 2
Memory management and prevent leakage
When the binding event listeners, if the listener is not remove, then the risk of memory leaks exist.
I know that the common practice is as follows:
- Often CR, remove unwanted event listeners
- By
once
binding the listener, after calling time, the listener is automatically removed - [Recommended] hack a safer
EventEmitter