// deno-lint-ignore-file /** The callback type. */ type Callback = (...args: any[]) => any | Promise; /** A listener type. */ type Listener = Callback & { __once__?: true }; /** The name of an event. */ type EventName = string | number; type EventsType = & { [key: string]: Callback } & { [key: number]: Callback }; /** * The event emitter. */ export class EventEmitter { /** * This is where the events and listeners are stored. */ private _events_: Map> = new Map(); /** * Listen for a typed event. * @param event The typed event name to listen for. * @param listener The typed listener function. */ public on(event: K, listener: E[K]): this; /** * Listen for an event. * @param event The event name to listen for. * @param listener The listener function. */ public on(event: EventName, listener: Callback): this { if (!this._events_.has(event)) this._events_.set(event, new Set()); this._events_.get(event)!.add(listener); return this; } /** * Listen for a typed event once. * @param event The typed event name to listen for. * @param listener The typed listener function. */ public once(event: K, listener: E[K]): this; /** * Listen for an event once. * @param event The event name to listen for. * @param listener The listener function. */ public once(event: EventName, listener: Callback): this { const l: Listener = listener; l.__once__ = true; return this.on(event, l as any); } /** * Remove a specific listener in the event emitter on a specific * typed event. * @param event The typed event name. * @param listener The typed event listener function. */ public off(event: K, listener: E[K]): this; /** * Remove all listeners on a specific typed event. * @param event The typed event name. */ public off(event: K): this; /** * Remove all events from the event listener. */ public off(): this; /** * Remove a specific listener on a specific event if both `event` * and `listener` is defined, or remove all listeners on a * specific event if only `event` is defined, or lastly remove * all listeners on every event if `event` is not defined. * @param event The event name. * @param listener The event listener function. */ public off(event?: EventName, listener?: Callback): this { if (!event && listener) { throw new Error("Why is there a listener defined here?"); } else if (!event && !listener) { this._events_.clear(); } else if (event && !listener) { this._events_.delete(event); } else if (event && listener && this._events_.has(event)) { const _ = this._events_.get(event)!; _.delete(listener); if (_.size === 0) this._events_.delete(event); } else { throw new Error("Unknown action!"); } return this; } /** * Emit a typed event without waiting for each listener to * return. * @param event The typed event name to emit. * @param args The arguments to pass to the typed listeners. */ public emitSync(event: K, ...args: Parameters): this; /** * Emit an event without waiting for each listener to return. * @param event The event name to emit. * @param args The arguments to pass to the listeners. */ public emitSync(event: EventName, ...args: Parameters): this { if (!this._events_.has(event)) return this; const _ = this._events_.get(event)!; for (let [, listener] of _.entries()) { const r = listener(...args); if (r instanceof Promise) r.catch(console.error); if (listener.__once__) { delete listener.__once__; _.delete(listener); } } if (_.size === 0) this._events_.delete(event); return this; } /** * Emit a typed event and wait for each typed listener to return. * @param event The typed event name to emit. * @param args The arguments to pass to the typed listeners. */ public async emit( event: K, ...args: Parameters ): Promise; /** * Emit an event and wait for each listener to return. * @param event The event name to emit. * @param args The arguments to pass to the listeners. */ public async emit( event: EventName, ...args: Parameters ): Promise { let returns: any[] = []; if (!this._events_.has(event)) return returns; const _ = this._events_.get(event)!; for (let [, listener] of _.entries()) { try { returns.push(await listener(...args)); if (listener.__once__) { delete listener.__once__; _.delete(listener); } } catch (error) { console.error(error); } } if (_.size === 0) this._events_.delete(event); return returns; } /** * The same as emitSync, but wait for each typed listener to * return before calling the next typed listener. * @param event The typed event name. * @param args The arguments to pass to the typed listeners. */ public queue(event: K, ...args: Parameters): this; /** * The same as emitSync, but wait for each listener to return * before calling the next listener. * @param event The event name. * @param args The arguments to pass to the listeners. */ public queue(event: EventName, ...args: Parameters): this { (async () => await this.emit(event, ...args as any))().catch(console.error); return this; } /** * Wait for a typed event to be emitted and return the arguments. * @param event The typed event name to wait for. * @param timeout An optional amount of milliseconds to wait * before throwing. */ public pull( event: K, timeout?: number, ): Promise>; /** * Wait for an event to be emitted and return the arguments. * @param event The event name to wait for. * @param timeout An optional amount of milliseconds to wait * before throwing. */ public pull( event: EventName, timeout?: number, ): Promise> { return new Promise(async (resolve, reject) => { let timeoutId: Timer | null; let listener = (...args: any[]) => { if (timeoutId !== null) clearTimeout(timeoutId); resolve(args); }; timeoutId = typeof timeout !== "number" ? null : setTimeout(() => (this.off(event, listener as any), reject( new Error("Timed out!"), )) ); this.once(event, listener as any); }); } } export default EventEmitter;