export interface WrappedFunction extends Function { listener: Function;}
export class EventEmitter { public static defaultMaxListeners = 10; public static errorMonitor = Symbol("events.errorMonitor"); private maxListeners: number | undefined; private _events: Map<string | symbol, Array<Function | WrappedFunction>>;
public constructor() { this._events = new Map(); }
private _addListener( eventName: string | symbol, listener: Function | WrappedFunction, prepend: boolean ): this { this.emit("newListener", eventName, listener); if (this._events.has(eventName)) { const listeners = this._events.get(eventName) as Array< Function | WrappedFunction >; if (prepend) { listeners.unshift(listener); } else { listeners.push(listener); } } else { this._events.set(eventName, [listener]); } const max = this.getMaxListeners(); if (max > 0 && this.listenerCount(eventName) > max) { const warning = new Error( `Possible EventEmitter memory leak detected. ${this.listenerCount(eventName)} ${eventName.toString()} listeners. Use emitter.setMaxListeners() to increase limit` ); warning.name = "MaxListenersExceededWarning"; console.warn(warning); }
return this; }
public addListener( eventName: string | symbol, listener: Function | WrappedFunction ): this { return this._addListener(eventName, listener, false); }
public emit(eventName: string | symbol, ...args: any[]): boolean { if (this._events.has(eventName)) { if ( eventName === "error" && this._events.get(EventEmitter.errorMonitor) ) { this.emit(EventEmitter.errorMonitor, ...args); } const listeners = (this._events.get(eventName) as Function[]).slice(); for (const listener of listeners) { try { listener.apply(this, args); } catch (err) { this.emit("error", err); } } return true; } else if (eventName === "error") { if (this._events.get(EventEmitter.errorMonitor)) { this.emit(EventEmitter.errorMonitor, ...args); } const errMsg = args.length > 0 ? args[0] : Error("Unhandled error."); throw errMsg; } return false; }
public eventNames(): [string | symbol] { return Array.from(this._events.keys()) as [string | symbol]; }
public getMaxListeners(): number { return this.maxListeners || EventEmitter.defaultMaxListeners; }
public listenerCount(eventName: string | symbol): number { if (this._events.has(eventName)) { return (this._events.get(eventName) as Function[]).length; } else { return 0; } }
private _listeners( target: EventEmitter, eventName: string | symbol, unwrap: boolean ): Function[] { if (!target._events.has(eventName)) { return []; } const eventListeners: Function[] = target._events.get( eventName ) as Function[];
return unwrap ? this.unwrapListeners(eventListeners) : eventListeners.slice(0); }
private unwrapListeners(arr: Function[]): Function[] { const unwrappedListeners: Function[] = new Array(arr.length) as Function[]; for (let i = 0; i < arr.length; i++) { unwrappedListeners[i] = (arr[i] as any)["listener"] || arr[i]; } return unwrappedListeners; }
public listeners(eventName: string | symbol): Function[] { return this._listeners(this, eventName, true); }
public rawListeners( eventName: string | symbol ): Array<Function | WrappedFunction> { return this._listeners(this, eventName, false); }
public off(eventName: string | symbol, listener: Function): this { return this.removeListener(eventName, listener); }
public on( eventName: string | symbol, listener: Function | WrappedFunction ): this { return this.addListener(eventName, listener); }
public once(eventName: string | symbol, listener: Function): this { const wrapped: WrappedFunction = this.onceWrap(eventName, listener); this.on(eventName, wrapped); return this; }
private onceWrap( eventName: string | symbol, listener: Function ): WrappedFunction { const wrapper = function ( this: { eventName: string | symbol; listener: Function; rawListener: Function; context: EventEmitter; }, ...args: any[] ): void { this.context.removeListener(this.eventName, this.rawListener); this.listener.apply(this.context, args); }; const wrapperContext = { eventName: eventName, listener: listener, rawListener: (wrapper as unknown) as WrappedFunction, context: this, }; const wrapped = (wrapper.bind( wrapperContext ) as unknown) as WrappedFunction; wrapperContext.rawListener = wrapped; wrapped.listener = listener; return wrapped as WrappedFunction; }
public prependListener( eventName: string | symbol, listener: Function | WrappedFunction ): this { return this._addListener(eventName, listener, true); }
public prependOnceListener( eventName: string | symbol, listener: Function ): this { const wrapped: WrappedFunction = this.onceWrap(eventName, listener); this.prependListener(eventName, wrapped); return this; }
public removeAllListeners(eventName?: string | symbol): this { if (this._events === undefined) { return this; }
if (eventName && this._events.has(eventName)) { const listeners = (this._events.get(eventName) as Array< Function | WrappedFunction >).slice(); this._events.delete(eventName); for (const listener of listeners) { this.emit("removeListener", eventName, listener); } } else { const eventList: [string | symbol] = this.eventNames(); eventList.map((value: string | symbol) => { this.removeAllListeners(value); }); }
return this; }
public removeListener(eventName: string | symbol, listener: Function): this { if (this._events.has(eventName)) { const arr: | Array<Function | WrappedFunction> | undefined = this._events.get(eventName);
if (arr == undefined) { return this }
let listenerIndex = -1; for (let i = arr.length - 1; i >= 0; i--) { if ( arr[i] == listener || (arr[i] && (arr[i] as WrappedFunction)["listener"] == listener) ) { listenerIndex = i; break; } }
if (listenerIndex >= 0) { arr.splice(listenerIndex, 1); this.emit("removeListener", eventName, listener); if (arr.length === 0) { this._events.delete(eventName); } } } return this; }
public setMaxListeners(n: number): this { this.maxListeners = n; return this; }}
const events = new EventEmitter();events.setMaxListeners(1 << 10); export default events;