技术规范
Runtime
- NodeJs 14.16.0
- npm 7.19.1
- TypeScript 4.1.5
开发(设计)工具
- Intellij IDEA
适用平台/语言
- TypeScript
- JavaScript
- 可移植
术语
'''监听事件''' 由事件名称和监听器组成
'''监听器''' 与事件名称绑定的上下文逻辑处理函数
'''事件驱动实例''' 事件驱动器的实例化对象
'''异步''' 一种消息的通讯机制,当未接收到预期返回结果时,不会引发进程阻塞。当接收到预期返回结果,再执行与结果相关的处理函数。
基础设计
通常,只需要考虑如下方法实现即可:
'''on'''
用于注册一个监听事件。
'''once'''
用于注册一个一次性监听事件,事件被触发后,监听器执行一次即销毁。
'''off'''
通过事件名称注销该事件名称下的全部监听事件,或指定事件名称和监听器注销指定事件。
'''emit'''
通过事件名称触发监听事件,并传入监听器所需要的参数。
'''listener'''
用于指定监听名称下的监听事件,或获取整个事件驱动实例下的全部监听事件。
'''destory'''
用于销毁整个事件驱动实例。注销后,所有监听事件全部一并销毁。
进阶设计
返回值接收、匿名监听器注销的相关问题。
事件驱动的监听器,大都难以获取返回值,通过异步特性,可以在监听器注册处获取返回值以供上下文环境使用。
而对于需要注销的匿名函数,通常只有一种注销办法,就是将匿名函数赋值给一个常量或变量(let或const),使函数具名化或直接声明具名函数,然后在函数内部执行事件驱动实例的off方法将监听器本身注销。
'''emitAsPromise'''
通常情况下,通过emit触发监听事件以后,注册的监听器没有任何返回值。emitAsPromise将在用户注册操作的形式参数最后添加异步函数resolve、reject,以便于监听事件触发后,通过Promise返回监听器结果。
'''safeEmit'''
通过safeEmit触发的监听事件,将在监听器形式参数的最后添加一个finish方法,在监听器内执行该方法即可将该监听器注销。
'''safeEmitAsPromise'''
safeEmitAsPromise结合了emitAsPromise和safeEmit的特性,通过safeEmitAsPromise触发的监听事件,会在监听器形式参数的最后添加finish、resolve、reject方法。
高级设计(一)
监听事件归类注册、管理的相关问题。
一个典型例子,对于web socket的消息事件(消息事件遵循事件驱动原则),若想将不同的消息事件与其处理逻辑做隔离,按照单一事务原则做处理,就一定会将socket以组合的形式绑定到处理逻辑内部。假设存在一个点对点的socket消息处理,存在一个点对多的socket消息处理,对这两个处理进行拆分,按类(或函数)进行封装,那么,socket就要组合到两个类(或函数)中。此时,若其中任意一个类的实例(或函数)执行完毕,需要销毁,此时就存在了socket消息处理逻辑的销毁问题。
对于使用了事件驱动的诸如上述的情况或工厂模式等模型,往往都存在这样一个顶层的事件驱动实例,所有产品都向同一个事件驱动实例注册事件,但产品销毁的时候,注册在顶层事件驱动实例中的监听事件就变的难以销毁,若监听器内部的判断不当,反而会导致不应该注销的监听器被异常注销,已经注销的实例方法被错误的执行等等问题。
因此,实现下列方法以解决上述问题:
'''createChild'''
创建一个子事件驱动实例,并在事件驱动实例中添加该事件驱动实例,任何一个触发监听事件的方法被执行,都会使子事件驱动实例中相同事件名称的监听器被执行,如果子监听器有其自己的子监听器,则该触发会通过深度优先的方式向下传播,直到驱动器自身的监听事件被执行完毕。
'''removeChild'''
移除实例中指定的子事件驱动实例,移除后,子事件驱动实例(及其自身的子事件驱动实例)将全部被销毁。
'''clearChild'''
移除实例中全部的子事件驱动实例。
'''destory'''
用于销毁整个实例,如果该事件驱动实例存在父事件驱动实例,将在父事件驱动实例中注销该事件。事件驱动实例销毁后,事件驱动实例及其子事件实例(如果有)将一并销毁。
示例
以下为使用typescript语言设计的示例
type listener = (...args: any) => any
class SpreadEmitter {
private readonly _parent: SpreadEmitter | null;
private readonly _child: Set<SpreadEmitter>;
private readonly _listeners: Map<string | symbol, Set<listener>>;
constructor(parent?: SpreadEmitter) {
if (parent) {
this._parent = parent;
} else {
this._parent = null;
}
this._child = new Set<SpreadEmitter>();
this._listeners = new Map<string | symbol, Set<listener>>();
}
public on(event: string | symbol, listener: listener): void {
let listeners = this._listeners.get(event);
if (!listeners) {
listeners = new Set<listener>();
this._listeners.set(event, listeners);
}
listeners.add(listener);
}
public once(event: string | symbol, listener: listener): void {
let listeners = this._listeners.get(event);
if (!listeners) {
listeners = new Set<listener>();
this._listeners.set(event, listeners);
}
const defer = (...args: any[]) => {
listener.apply(this, args);
this.off(event, defer);
};
listeners.add(defer);
}
public off(event: string | symbol, listener?: listener): void {
if (!listener) {
this._listeners.delete(event);
return;
}
const listeners = this._listeners.get(event);
if (listeners) {
listeners.delete(listener);
}
}
public emit(event: string | symbol, ...args: any[]): void {
this._child.forEach((child) => {
child.emit(event, ...args);
});
const listeners = this._listeners.get(event);
if (!listeners) {
return;
}
Object.assign([], Array.from(listeners)).forEach((listener: any) => {
try {
listener.apply(this, args);
} catch (e) {
this.off(event, listener);
console.error("listener execute error, event: %s, listener: %o. error: %o", event, listener, e);
}
});
}
public emitAsPromise<T>(event: string | symbol, ...args: any[]): Promise<T | any> {
return new Promise((resolve, reject) => {
try {
this.emit(event, ...args, resolve, reject);
} catch (e) {
console.error(e);
}
});
}
public safeEmit(event: string | symbol, ...args: any[]): void {
this._child.forEach((child) => {
child.safeEmit(event, ...args);
});
const listeners = this._listeners.get(event);
if (!listeners) {
return;
}
if (args === undefined) {
args = [];
}
Object.assign([], Array.from(listeners)).forEach((listener: any) => {
try {
const callback = () => (this.off(event, listener));
const [...params] = args;
params.push(callback);
listener.apply(this, params);
} catch (e) {
this.off(event, listener);
console.error("listener execute error, event: %s, listener: %o. error: %o", event, listener, e);
}
});
}
public safeEmitAsPromise<T>(event: string | symbol, ...args: any[]): Promise<T | any> {
return new Promise((resolve, reject) => {
try {
this.safeEmit(event, ...args, resolve, reject);
} catch (e) {
console.error(e);
}
});
}
public listeners(event?: string):
Set<listener> | Map<string | symbol, Set<listener>> | undefined {
if (event) {
return this._listeners.get(event);
}
return this._listeners;
}
public createChild(): SpreadEmitter {
const child = new SpreadEmitter(this);
this._child.add(child);
return child;
}
public removeChild(child: SpreadEmitter): boolean {
return this._child.delete(child);
}
public clearChild(): void {
return this._child.clear();
}
public destroy(): void {
this._listeners.clear();
this._parent?.removeChild(this);
this._child.clear();
}
}
export default SpreadEmitter;
DISCUSSION
评论
还没有公开评论,欢迎留下想法。