export type EventType = string | symbol; // An event handler can take an optional event argument // and should not return a value export type Handler = (event: T) => void; export type WildcardHandler> = ( type: keyof T, event: T[keyof T] ) => void; // An array of all currently registered event handlers for a type export type EventHandlerList = Array>; export type WildCardEventHandlerList> = Array< WildcardHandler >; // A map of event types and their corresponding event handlers. export type EventHandlerMap> = Map< keyof Events | '*', EventHandlerList | WildCardEventHandlerList >; export interface Emitter> { all: EventHandlerMap; on(type: Key, handler: Handler): void; on(type: '*', handler: WildcardHandler): void; once(type: Key, handler: Handler): void; once(type: '*', handler: WildcardHandler): void; off( type: Key, handler?: Handler ): void; off(type: '*', handler: WildcardHandler): void; emit(type: Key, event: Events[Key]): void; emit( type: undefined extends Events[Key] ? Key : never ): void; when(type: Key): Promise; } /** * Nitt: Tiny (~200b) functional event emitter / pubsub. * @name nitt * @returns {Nitt} */ export default function nitt>( all?: EventHandlerMap ): Emitter { type GenericEventHandler = | Handler | WildcardHandler; all = all || new Map(); return { /** * A Map of event names to registered handler functions. */ all, /** * Register an event handler for the given type. * @param {string|symbol} type Type of event to listen for, or `'*'` for all events * @param {Function} handler Function to call in response to given event * @memberOf nitt */ on(type: Key, handler: GenericEventHandler) { const handlers: Array | undefined = all!.get(type); if (handlers) { handlers.push(handler); } else { all!.set(type, [handler] as EventHandlerList); } }, /** * Remove an event handler for the given type. * If `handler` is omitted, all handlers of the given type are removed. * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) * @param {Function} [handler] Handler function to remove * @memberOf nitt */ off(type: Key, handler?: GenericEventHandler) { const handlers: Array | undefined = all!.get(type); if (handlers) { if (handler) { handlers.splice(handlers.indexOf(handler) >>> 0, 1); } else { all!.set(type, []); } } }, /** * Invoke all handlers for the given type. * If present, `'*'` handlers are invoked after type-matched handlers. * * Note: Manually firing '*' handlers is not supported. * * @param {string|symbol} type The event type to invoke * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler * @memberOf nitt */ emit(type: Key, evt?: Events[Key]) { let handlers = all!.get(type); if (handlers) { (handlers as EventHandlerList) .slice() .map((handler) => { handler(evt!); }); } handlers = all!.get('*'); if (handlers) { (handlers as WildCardEventHandlerList) .slice() .map((handler) => { handler(type, evt!); }); } }, /** * Register an event handler that is executed just once for the given type. * * @param {string|symbol} type Type of event to listen for, or `'*'` for all events * @param {Function} handler Function to call in response to given event * @memberOf nitt */ once(type: Key, handler: GenericEventHandler) { if (type === '*') { const onceHandler: WildcardHandler = (type: keyof Events, event: Events[keyof Events]) => { (handler as WildcardHandler)(type, event); this.off('*', onceHandler); }; this.on('*', onceHandler); } else { const onceHandler = (evt: Events[Key]) => { (handler as Handler)(evt); this.off(type, onceHandler); }; this.on(type, onceHandler); } }, /** * Returns a promise for a single event * * @param {String} type Type of event to listen for, or `"*"` for any event * @returns {Promise} */ when(type: Key): Promise { return new Promise((resolve: GenericEventHandler) => { if (type === '*') { this.once('*', (resolve as WildcardHandler)); } else { this.once(type, (resolve as Handler)); } }); } }; }