Files
douboer@gmail.com 3b7c1d558a first commit
2026-03-03 13:23:14 +08:00

78 lines
2.2 KiB
TypeScript

import Disposable from "../base/disposable";
import type {
XEventEmitter as IEventEmitter,
IEventListener,
IEventName
} from "../types";
import type { IEmitterState } from "./interface";
/**
* EventEmitter class
*
* https://nodejs.dev/api/events.html
*
* https://nodejs.dev/en/learn/the-nodejs-event-emitter/
*/
export default class XEventEmitter extends Disposable implements IEventEmitter {
// private store for states
#state: IEmitterState;
constructor() {
super();
this.#state = {
stack: [],
store: new Map()
};
this.register({ dispose: () => this.#state.store.clear() });
}
public on(eventName: IEventName, listener: IEventListener): void {
const store = this.#state.store;
if (store.has(eventName)) {
store.get(eventName)?.add(listener);
} else {
store.set(eventName, new Set([listener]));
}
}
public once(eventName: IEventName, listener: IEventListener): void {
const evlistener = (...args: unknown[]) => {
listener.call(undefined, ...args);
this.off(eventName, evlistener);
};
const store = this.#state.store;
if (store.has(eventName)) {
store.get(eventName)?.add(evlistener);
} else {
store.set(eventName, new Set([evlistener]));
}
}
public off(eventName: IEventName, listener: IEventListener): void {
const all = this.#state.store.get(eventName);
if (all) {
for (const fn of all) {
if (fn === listener) {
all.delete(listener);
break;
}
}
}
}
public emit(eventName: IEventName, ...args: unknown[]): void {
if (this.isDisposed) return;
const stack = this.#state.stack;
if (stack.includes(eventName)) return;
const listeners = this.#state.store.get(eventName);
if (listeners) {
stack.push(eventName);
for (const fn of listeners) {
fn.call(undefined, ...args);
}
stack.pop();
}
return;
}
}