EventEmitter.ts (5383B)
1 /** 2 * @license 3 * Copyright 2022 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import mitt, {type Emitter} from '../../third_party/mitt/mitt.js'; 8 import {disposeSymbol} from '../util/disposable.js'; 9 10 /** 11 * @public 12 */ 13 export type EventType = string | symbol; 14 15 /** 16 * @public 17 */ 18 export type Handler<T = unknown> = (event: T) => void; 19 20 /** 21 * @public 22 */ 23 export interface CommonEventEmitter<Events extends Record<EventType, unknown>> { 24 on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): this; 25 off<Key extends keyof Events>( 26 type: Key, 27 handler?: Handler<Events[Key]>, 28 ): this; 29 emit<Key extends keyof Events>(type: Key, event: Events[Key]): boolean; 30 once<Key extends keyof Events>( 31 type: Key, 32 handler: Handler<Events[Key]>, 33 ): this; 34 listenerCount(event: keyof Events): number; 35 36 removeAllListeners(event?: keyof Events): this; 37 } 38 39 /** 40 * @public 41 */ 42 export type EventsWithWildcard<Events extends Record<EventType, unknown>> = 43 Events & { 44 '*': Events[keyof Events]; 45 }; 46 47 /** 48 * The EventEmitter class that many Puppeteer classes extend. 49 * 50 * @remarks 51 * 52 * This allows you to listen to events that Puppeteer classes fire and act 53 * accordingly. Therefore you'll mostly use {@link EventEmitter.on | on} and 54 * {@link EventEmitter.off | off} to bind 55 * and unbind to event listeners. 56 * 57 * @public 58 */ 59 export class EventEmitter<Events extends Record<EventType, unknown>> 60 implements CommonEventEmitter<EventsWithWildcard<Events>> 61 { 62 #emitter: Emitter<EventsWithWildcard<Events>> | EventEmitter<Events>; 63 #handlers = new Map<keyof Events | '*', Array<Handler<any>>>(); 64 65 /** 66 * If you pass an emitter, the returned emitter will wrap the passed emitter. 67 * 68 * @internal 69 */ 70 constructor( 71 emitter: Emitter<EventsWithWildcard<Events>> | EventEmitter<Events> = mitt( 72 new Map(), 73 ), 74 ) { 75 this.#emitter = emitter; 76 } 77 78 /** 79 * Bind an event listener to fire when an event occurs. 80 * @param type - the event type you'd like to listen to. Can be a string or symbol. 81 * @param handler - the function to be called when the event occurs. 82 * @returns `this` to enable you to chain method calls. 83 */ 84 on<Key extends keyof EventsWithWildcard<Events>>( 85 type: Key, 86 handler: Handler<EventsWithWildcard<Events>[Key]>, 87 ): this { 88 const handlers = this.#handlers.get(type); 89 if (handlers === undefined) { 90 this.#handlers.set(type, [handler]); 91 } else { 92 handlers.push(handler); 93 } 94 95 this.#emitter.on(type, handler); 96 return this; 97 } 98 99 /** 100 * Remove an event listener from firing. 101 * @param type - the event type you'd like to stop listening to. 102 * @param handler - the function that should be removed. 103 * @returns `this` to enable you to chain method calls. 104 */ 105 off<Key extends keyof EventsWithWildcard<Events>>( 106 type: Key, 107 handler?: Handler<EventsWithWildcard<Events>[Key]>, 108 ): this { 109 const handlers = this.#handlers.get(type) ?? []; 110 if (handler === undefined) { 111 for (const handler of handlers) { 112 this.#emitter.off(type, handler); 113 } 114 this.#handlers.delete(type); 115 return this; 116 } 117 const index = handlers.lastIndexOf(handler); 118 if (index > -1) { 119 this.#emitter.off(type, ...handlers.splice(index, 1)); 120 } 121 return this; 122 } 123 124 /** 125 * Emit an event and call any associated listeners. 126 * 127 * @param type - the event you'd like to emit 128 * @param eventData - any data you'd like to emit with the event 129 * @returns `true` if there are any listeners, `false` if there are not. 130 */ 131 emit<Key extends keyof EventsWithWildcard<Events>>( 132 type: Key, 133 event: EventsWithWildcard<Events>[Key], 134 ): boolean { 135 this.#emitter.emit(type, event); 136 return this.listenerCount(type) > 0; 137 } 138 139 /** 140 * Like `on` but the listener will only be fired once and then it will be removed. 141 * @param type - the event you'd like to listen to 142 * @param handler - the handler function to run when the event occurs 143 * @returns `this` to enable you to chain method calls. 144 */ 145 once<Key extends keyof EventsWithWildcard<Events>>( 146 type: Key, 147 handler: Handler<EventsWithWildcard<Events>[Key]>, 148 ): this { 149 const onceHandler: Handler<EventsWithWildcard<Events>[Key]> = eventData => { 150 handler(eventData); 151 this.off(type, onceHandler); 152 }; 153 154 return this.on(type, onceHandler); 155 } 156 157 /** 158 * Gets the number of listeners for a given event. 159 * 160 * @param type - the event to get the listener count for 161 * @returns the number of listeners bound to the given event 162 */ 163 listenerCount(type: keyof EventsWithWildcard<Events>): number { 164 return this.#handlers.get(type)?.length || 0; 165 } 166 167 /** 168 * Removes all listeners. If given an event argument, it will remove only 169 * listeners for that event. 170 * 171 * @param type - the event to remove listeners for. 172 * @returns `this` to enable you to chain method calls. 173 */ 174 removeAllListeners(type?: keyof EventsWithWildcard<Events>): this { 175 if (type !== undefined) { 176 return this.off(type); 177 } 178 this[disposeSymbol](); 179 return this; 180 } 181 182 /** 183 * @internal 184 */ 185 [disposeSymbol](): void { 186 for (const [type, handlers] of this.#handlers) { 187 for (const handler of handlers) { 188 this.#emitter.off(type, handler); 189 } 190 } 191 this.#handlers.clear(); 192 } 193 }