| 24 | * ``` |
| 25 | */ |
| 26 | export class EventEmitter<T> { |
| 27 | readonly #listeners = new Set<(e: T) => void>() |
| 28 | |
| 29 | /** |
| 30 | * The event that listeners can subscribe to |
| 31 | * |
| 32 | * @param listener - The callback function to invoke when the event fires |
| 33 | * @param thisArgs - Optional 'this' context for the listener |
| 34 | * @param disposables - Optional array to add the disposable to |
| 35 | * @returns A disposable to unsubscribe from the event |
| 36 | */ |
| 37 | event: Event<T> = (listener: (e: T) => void, thisArgs?: unknown, disposables?: Disposable[]): Disposable => { |
| 38 | const fn = thisArgs ? listener.bind(thisArgs) : listener |
| 39 | this.#listeners.add(fn) |
| 40 | |
| 41 | const disposable: Disposable = { |
| 42 | dispose: () => { |
| 43 | this.#listeners.delete(fn) |
| 44 | }, |
| 45 | } |
| 46 | |
| 47 | if (disposables) { |
| 48 | disposables.push(disposable) |
| 49 | } |
| 50 | |
| 51 | return disposable |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Fire the event, notifying all subscribers |
| 56 | * |
| 57 | * Failure of one or more listeners will not fail this function call. |
| 58 | * Failed listeners will be caught and ignored to prevent one listener |
| 59 | * from breaking others. |
| 60 | * |
| 61 | * @param data - The event data to pass to listeners |
| 62 | */ |
| 63 | fire(data: T): void { |
| 64 | for (const listener of this.#listeners) { |
| 65 | try { |
| 66 | listener(data) |
| 67 | } catch (error) { |
| 68 | // Silently ignore listener errors to prevent one failing listener |
| 69 | // from affecting others. Consumers can add error handling in their listeners. |
| 70 | console.error("EventEmitter listener error:", error) |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Dispose this event emitter and remove all listeners |
| 77 | */ |
| 78 | dispose(): void { |
| 79 | this.#listeners.clear() |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Get the current number of listeners (useful for debugging) |