import { assert } from "../_util/assert.ts";import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "./_errors.ts";import { inspect } from "./util.ts";
export type GenericFunction = (...args: any[]) => any;
export interface WrappedFunction extends Function { listener: GenericFunction;}
function ensureArray<T>(maybeArray: T[] | T): T[] { return Array.isArray(maybeArray) ? maybeArray : [maybeArray];}
function createIterResult(value: any, done: boolean): IteratorResult<any> { return { value, done };}
interface AsyncIterable { next(): Promise<IteratorResult<any, any>>; return(): Promise<IteratorResult<any, any>>; throw(err: Error): void; [Symbol.asyncIterator](): any;}
type EventMap = Record< string | symbol, ( | (Array<GenericFunction | WrappedFunction>) | GenericFunction | WrappedFunction ) & { warned?: boolean }>;
export let defaultMaxListeners = 10;function validateMaxListeners(n: number, name: string): void { if (!Number.isInteger(n) || n < 0) { throw new ERR_OUT_OF_RANGE(name, "a non-negative number", inspect(n)); }}
export class EventEmitter { public static captureRejectionSymbol = Symbol.for("nodejs.rejection"); public static errorMonitor = Symbol("events.errorMonitor"); public static get defaultMaxListeners() { return defaultMaxListeners; } public static set defaultMaxListeners(value: number) { validateMaxListeners(value, "defaultMaxListeners"); defaultMaxListeners = value; }
private maxListeners: number | undefined; private _events: EventMap;
public constructor() { this._events = Object.create(null); }
private _addListener( eventName: string | symbol, listener: GenericFunction | WrappedFunction, prepend: boolean, ): this { this.checkListenerArgument(listener); this.emit("newListener", eventName, this.unwrapListener(listener)); if (this.hasListeners(eventName)) { let listeners = this._events[eventName]; if (!Array.isArray(listeners)) { listeners = [listeners]; this._events[eventName] = listeners; }
if (prepend) { listeners.unshift(listener); } else { listeners.push(listener); } } else { this._events[eventName] = listener; } const max = this.getMaxListeners(); if (max > 0 && this.listenerCount(eventName) > max) { const warning = new MaxListenersExceededWarning(this, eventName); this.warnIfNeeded(eventName, warning); }
return this; }
addListener( eventName: string | symbol, listener: GenericFunction | WrappedFunction, ): this { return this._addListener(eventName, listener, false); }
public emit(eventName: string | symbol, ...args: any[]): boolean { if (this.hasListeners(eventName)) { if ( eventName === "error" && this.hasListeners(EventEmitter.errorMonitor) ) { this.emit(EventEmitter.errorMonitor, ...args); }
const listeners = ensureArray<GenericFunction>(this._events[eventName]) .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.hasListeners(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 Reflect.ownKeys(this._events) as [string | symbol]; }
public getMaxListeners(): number { return this.maxListeners == null ? EventEmitter.defaultMaxListeners : this.maxListeners; }
public listenerCount(eventName: string | symbol): number { if (this.hasListeners(eventName)) { const maybeListeners = this._events[eventName]; return Array.isArray(maybeListeners) ? maybeListeners.length : 1; } else { return 0; } }
static listenerCount( emitter: EventEmitter, eventName: string | symbol, ): number { return emitter.listenerCount(eventName); }
private _listeners( target: EventEmitter, eventName: string | symbol, unwrap: boolean, ): GenericFunction[] { if (!target.hasListeners(eventName)) { return []; }
const eventListeners = target._events[eventName]; if (Array.isArray(eventListeners)) { return unwrap ? this.unwrapListeners(eventListeners) : eventListeners.slice(0) as GenericFunction[]; } else { return [ unwrap ? this.unwrapListener(eventListeners) : eventListeners, ] as GenericFunction[]; } }
private unwrapListeners( arr: (GenericFunction | WrappedFunction)[], ): GenericFunction[] { const unwrappedListeners = new Array(arr.length) as GenericFunction[]; for (let i = 0; i < arr.length; i++) { unwrappedListeners[i] = this.unwrapListener(arr[i]); } return unwrappedListeners; }
private unwrapListener( listener: GenericFunction | WrappedFunction, ): GenericFunction { return (listener as WrappedFunction)["listener"] ?? listener; }
public listeners(eventName: string | symbol): GenericFunction[] { return this._listeners(this, eventName, true); }
public rawListeners( eventName: string | symbol, ): Array<GenericFunction | WrappedFunction> { return this._listeners(this, eventName, false); }
public off( eventName: string | symbol, listener: GenericFunction, ): this { }
public on( eventName: string | symbol, listener: GenericFunction | WrappedFunction, ): this { }
public once(eventName: string | symbol, listener: GenericFunction): this { const wrapped: WrappedFunction = this.onceWrap(eventName, listener); this.on(eventName, wrapped); return this; }
private onceWrap( eventName: string | symbol, listener: GenericFunction, ): WrappedFunction { this.checkListenerArgument(listener); const wrapper = function ( this: { eventName: string | symbol; listener: GenericFunction; rawListener: GenericFunction | WrappedFunction; context: EventEmitter; isCalled?: boolean; }, ...args: any[] ): void { if (this.isCalled) { return; } this.context.removeListener( this.eventName, this.listener as GenericFunction, ); this.isCalled = true; return 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: GenericFunction | WrappedFunction, ): this { return this._addListener(eventName, listener, true); }
public prependOnceListener( eventName: string | symbol, listener: GenericFunction, ): 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) { if (this.hasListeners(eventName)) { const listeners = ensureArray(this._events[eventName]).slice() .reverse(); for (const listener of listeners) { this.removeListener( eventName, this.unwrapListener(listener), ); } } } else { const eventList = this.eventNames(); eventList.forEach((eventName: string | symbol) => { if (eventName === "removeListener") return; this.removeAllListeners(eventName); }); this.removeAllListeners("removeListener"); }
return this; }
public removeListener( eventName: string | symbol, listener: GenericFunction, ): this { this.checkListenerArgument(listener); if (this.hasListeners(eventName)) { const maybeArr = this._events[eventName];
assert(maybeArr); const arr = ensureArray(maybeArr);
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); if (arr.length === 0) { delete this._events[eventName]; } else if (arr.length === 1) { this._events[eventName] = arr[0]; }
if (this._events.removeListener) { this.emit("removeListener", eventName, listener); } } } return this; }
public setMaxListeners(n: number): this { if (n !== Infinity) { validateMaxListeners(n, "n"); }
this.maxListeners = n; return this; }
public static once( emitter: EventEmitter | EventTarget, name: string, ): Promise<any[]> { return new Promise((resolve, reject) => { if (emitter instanceof EventTarget) { emitter.addEventListener( name, (...args) => { resolve(args); }, { once: true, passive: false, capture: false }, ); return; } else if (emitter instanceof EventEmitter) { const eventListener = (...args: any[]): void => { if (errorListener !== undefined) { emitter.removeListener("error", errorListener); } resolve(args); }; let errorListener: GenericFunction;
if (name !== "error") { errorListener = (err: any): void => { emitter.removeListener(name, eventListener); reject(err); };
emitter.once("error", errorListener); }
emitter.once(name, eventListener); return; } }); }
public static on( emitter: EventEmitter, event: string | symbol, ): AsyncIterable { const unconsumedEventValues: any[] = []; const unconsumedPromises: any[] = []; let error: Error | null = null; let finished = false;
const iterator = { next(): Promise<IteratorResult<any>> { const value: any = unconsumedEventValues.shift(); if (value) { return Promise.resolve(createIterResult(value, false)); }
if (error) { const p: Promise<never> = Promise.reject(error); error = null; return p; }
if (finished) { return Promise.resolve(createIterResult(undefined, true)); }
return new Promise(function (resolve, reject) { unconsumedPromises.push({ resolve, reject }); }); },
return(): Promise<IteratorResult<any>> { emitter.removeListener(event, eventHandler); emitter.removeListener("error", errorHandler); finished = true;
for (const promise of unconsumedPromises) { promise.resolve(createIterResult(undefined, true)); }
return Promise.resolve(createIterResult(undefined, true)); },
throw(err: Error): void { error = err; emitter.removeListener(event, eventHandler); emitter.removeListener("error", errorHandler); },
[Symbol.asyncIterator](): any { return this; }, };
emitter.on(event, eventHandler); emitter.on("error", errorHandler);
return iterator;
function eventHandler(...args: any[]): void { const promise = unconsumedPromises.shift(); if (promise) { promise.resolve(createIterResult(args, false)); } else { unconsumedEventValues.push(args); } }
function errorHandler(err: any): void { finished = true;
const toError = unconsumedPromises.shift(); if (toError) { toError.reject(err); } else { error = err; }
iterator.return(); } }
private checkListenerArgument(listener: unknown): void { if (typeof listener !== "function") { throw new ERR_INVALID_ARG_TYPE("listener", "function", listener); } }
private warnIfNeeded(eventName: string | symbol, warning: Error): void { const listeners = this._events[eventName]; if (listeners.warned) { return; } listeners.warned = true; console.warn(warning);
const maybeProcess = (globalThis as any).process; if (maybeProcess instanceof EventEmitter) { maybeProcess.emit("warning", warning); } }
private hasListeners(eventName: string | symbol): boolean { return this._events && Boolean(this._events[eventName]); }}
EventEmitter.prototype.on = EventEmitter.prototype.addListener;EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
class MaxListenersExceededWarning extends Error { readonly count: number; constructor( readonly emitter: EventEmitter, readonly type: string | symbol, ) { const listenerCount = emitter.listenerCount(type); const message = "Possible EventEmitter memory leak detected. " + `${listenerCount} ${ type == null ? "null" : type.toString() } listeners added to [${emitter.constructor.name}]. ` + " Use emitter.setMaxListeners() to increase limit"; super(message); this.count = listenerCount; this.name = "MaxListenersExceededWarning"; }}
export default Object.assign(EventEmitter, { EventEmitter });
export const captureRejectionSymbol = EventEmitter.captureRejectionSymbol;export const errorMonitor = EventEmitter.errorMonitor;export const listenerCount = EventEmitter.listenerCount;export const on = EventEmitter.on;export const once = EventEmitter.once;