@haelp/teto
Version:
A typescript-based controllable TETR.IO client.
103 lines (85 loc) • 2.55 kB
text/typescript
export class EventEmitter<T extends Record<string, any>> {
#listeners: [keyof T, Function, boolean][];
#maxListeners: number = 10;
/** Enables more debugging logs for memory leaks */
verbose = false;
constructor() {
this.#listeners = [];
}
on<K extends keyof T>(event: K, cb: (data: T[K]) => void) {
this.#listeners.push([event, cb, false]);
const listeners = this.#listeners.filter(([e]) => e === event);
if (listeners.length > this.#maxListeners) {
console.warn(
`Max listeners exceeded for event "${String(event)}". Current: ${
this.#listeners.filter(([e]) => e === event).length
}, Max: ${this.#maxListeners}`
);
if (this.verbose)
console.warn(
`Trace: ${new Error().stack}\n\nListeners:\n`,
listeners.map(([_, fn]) => fn.toString()).join("\n\n")
);
}
return this;
}
off<K extends keyof T>(event: K, cb: (data: T[K]) => void) {
this.#listeners = this.#listeners.filter(
([e, c]) => e !== event || c !== cb
);
return this;
}
emit<K extends keyof T>(event: K, data: T[K]) {
const toRemove = new Set<number>();
this.#listeners.forEach(([e, cb, once], idx) => {
if (e !== event) return;
cb(data);
if (once) toRemove.add(idx);
});
this.#listeners = this.#listeners.filter((_, idx) => !toRemove.has(idx));
return this;
}
once<K extends keyof T>(event: K, cb: (data: T[K]) => any | Promise<any>) {
this.#listeners.push([event, cb, true]);
return this;
}
removeAllListeners<K extends keyof T>(event?: K) {
if (event) {
this.#listeners = this.#listeners.filter(([e]) => e !== event);
} else {
this.#listeners = [];
}
}
set maxListeners(n: number) {
if (n <= 0 || !Number.isInteger(n)) {
throw new RangeError("Max listeners must be a positive integer");
}
this.#maxListeners = n;
}
get maxListeners() {
return this.#maxListeners;
}
export() {
return {
listeners: this.#listeners.map(([event, cb, once]) => ({
event,
cb,
once
})),
maxListeners: this.#maxListeners,
verbose: this.verbose
};
}
import(data: ReturnType<EventEmitter<T>["export"]>) {
data.listeners.forEach(({ event, cb, once }) => {
if (once) {
this.once(event, cb as any);
} else {
this.on(event, cb as any);
}
});
this.#maxListeners = data.maxListeners;
this.verbose = data.verbose;
return this;
}
}