|
| 1 | +type EventListener<Events, EventType extends keyof Events> = Events[EventType]; |
| 2 | + |
| 3 | +type EventListeners<Events, EventType extends keyof Events> = Array<{ |
| 4 | + listener: EventListener<Events, EventType>; |
| 5 | + once?: boolean; |
| 6 | +}>; |
| 7 | + |
| 8 | +export type EventParameters<Events, EventType extends keyof Events> = { |
| 9 | + [Event in EventType]: EventListener<Events, EventType> extends (...args: infer P) => any ? P : never; |
| 10 | +}[EventType]; |
| 11 | + |
| 12 | +export class EventEmitter<EventTypes extends Record<string, (...args: any) => any>> { |
| 13 | + #listeners: { |
| 14 | + [Event in keyof EventTypes]?: EventListeners<EventTypes, Event>; |
| 15 | + } = {}; |
| 16 | + |
| 17 | + /** |
| 18 | + * Adds the listener function to the end of the listeners array for the event. |
| 19 | + * No checks are made to see if the listener has already been added. Multiple calls passing |
| 20 | + * the same combination of event and listener will result in the listener being added, and |
| 21 | + * called, multiple times. |
| 22 | + * @returns this, so that calls can be chained |
| 23 | + */ |
| 24 | + on<Event extends keyof EventTypes>(event: Event, listener: EventListener<EventTypes, Event>): this { |
| 25 | + const listeners: EventListeners<EventTypes, Event> = |
| 26 | + this.#listeners[event] || (this.#listeners[event] = []); |
| 27 | + listeners.push({ listener }); |
| 28 | + return this; |
| 29 | + } |
| 30 | + |
| 31 | + /** |
| 32 | + * Removes the specified listener from the listener array for the event. |
| 33 | + * off() will remove, at most, one instance of a listener from the listener array. If any single |
| 34 | + * listener has been added multiple times to the listener array for the specified event, then |
| 35 | + * off() must be called multiple times to remove each instance. |
| 36 | + * @returns this, so that calls can be chained |
| 37 | + */ |
| 38 | + off<Event extends keyof EventTypes>(event: Event, listener: EventListener<EventTypes, Event>): this { |
| 39 | + const listeners = this.#listeners[event]; |
| 40 | + if (!listeners) return this; |
| 41 | + const index = listeners.findIndex((l) => l.listener === listener); |
| 42 | + if (index >= 0) listeners.splice(index, 1); |
| 43 | + return this; |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * Adds a one-time listener function for the event. The next time the event is triggered, |
| 48 | + * this listener is removed and then invoked. |
| 49 | + * @returns this, so that calls can be chained |
| 50 | + */ |
| 51 | + once<Event extends keyof EventTypes>(event: Event, listener: EventListener<EventTypes, Event>): this { |
| 52 | + const listeners: EventListeners<EventTypes, Event> = |
| 53 | + this.#listeners[event] || (this.#listeners[event] = []); |
| 54 | + listeners.push({ listener, once: true }); |
| 55 | + return this; |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * This is similar to `.once()`, but returns a Promise that resolves the next time |
| 60 | + * the event is triggered, instead of calling a listener callback. |
| 61 | + * @returns a Promise that resolves the next time given event is triggered, |
| 62 | + * or rejects if an error is emitted. (If you request the 'error' event, |
| 63 | + * returns a promise that resolves with the error). |
| 64 | + * |
| 65 | + * Example: |
| 66 | + * |
| 67 | + * const message = await stream.emitted('message') // rejects if the stream errors |
| 68 | + */ |
| 69 | + emitted<Event extends keyof EventTypes>( |
| 70 | + event: Event, |
| 71 | + ): Promise< |
| 72 | + EventParameters<EventTypes, Event> extends [infer Param] ? Param |
| 73 | + : EventParameters<EventTypes, Event> extends [] ? void |
| 74 | + : EventParameters<EventTypes, Event> |
| 75 | + > { |
| 76 | + return new Promise((resolve, reject) => { |
| 77 | + // TODO: handle errors |
| 78 | + this.once(event, resolve as any); |
| 79 | + }); |
| 80 | + } |
| 81 | + |
| 82 | + protected _emit<Event extends keyof EventTypes>( |
| 83 | + this: EventEmitter<EventTypes>, |
| 84 | + event: Event, |
| 85 | + ...args: EventParameters<EventTypes, Event> |
| 86 | + ) { |
| 87 | + const listeners: EventListeners<EventTypes, Event> | undefined = this.#listeners[event]; |
| 88 | + if (listeners) { |
| 89 | + this.#listeners[event] = listeners.filter((l) => !l.once) as any; |
| 90 | + listeners.forEach(({ listener }: any) => listener(...(args as any))); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + protected _hasListener(event: keyof EventTypes): boolean { |
| 95 | + const listeners = this.#listeners[event]; |
| 96 | + return listeners && listeners.length > 0; |
| 97 | + } |
| 98 | +} |
0 commit comments