@t8/event-patterns
Version:
Lightweight event emitter with flexible event type matching
86 lines (65 loc) • 2.33 kB
text/typescript
import {MatchParams, matchPattern} from './matchPattern';
export type EventType = string | number | boolean | RegExp | null | undefined;
export type EventHandler = (event: Event) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Event<T = any> = {
type: EventType;
params?: MatchParams | null;
data: T;
};
export type EventListener = {
id: string;
type: EventType;
handler: EventHandler;
once: boolean;
};
function getId() {
return Math.random().toString(36).slice(2);
}
export class EventEmitter {
_listeners: EventListener[];
constructor() {
this._listeners = [];
}
on(type: EventType, handler: EventHandler) {
return this._addListener(type, handler);
}
once(type: EventType, handler: EventHandler) {
return this._addListener(type, handler, true);
}
addListener(type: EventType, handler: EventHandler) {
return this._addListener(type, handler);
}
_addListener(type: EventType, handler: EventHandler, once = false) {
if (typeof handler !== 'function')
throw new Error('handler is not a function');
let id = getId();
this._listeners.push({id, type, handler, once});
return {
remove: () => this._removeListener(id),
};
}
_removeListener(id: string) {
for (let i = this._listeners.length - 1; i >= 0; i--) {
if (this._listeners[i].id === id)
this._listeners.splice(i, 1);
}
}
emit(type: EventType, data?: unknown): void {
let event: Event = {type, data};
for (let listener of this._listeners) {
if (this.matches(listener, event)) {
if (listener.once)
this._removeListener(listener.id);
listener.handler(this.toHandlerPayload(listener, event));
}
}
}
matches(listener: EventListener, event: Partial<Event>): boolean {
return matchPattern(listener.type, event.type) !== null;
}
toHandlerPayload(listener: EventListener, event: Event): Event {
let params = matchPattern(listener.type, event.type);
return {...event, params};
}
}