Skip to content

Instantly share code, notes, and snippets.

@rusintez
Last active April 9, 2024 05:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rusintez/1b9af057eb4160fe1e8d28784d099238 to your computer and use it in GitHub Desktop.
Save rusintez/1b9af057eb4160fe1e8d28784d099238 to your computer and use it in GitHub Desktop.
Typescript Event Emitter
/**
* Strongly typed event emitter
*
* @usage
*
* const emitter = createEmitter<{
* signature: [{ publicKey: number[]; signature: number[] }];
* }>();
*
* emitter.on("signature", ({ publicKey, signature }) => {
* console.log({ publicKey, signature });
* });
*/
export type IEventMap = Record<string, any[]>;
export type IEventNames<Events extends IEventMap> = Exclude<
keyof Events,
number | symbol
>;
export type IEventParams<
Events extends IEventMap,
Name extends IEventNames<Events>
> = Events[Name];
export type IEventHandler<
Events extends IEventMap,
Name extends IEventNames<Events>
> = (...args: IEventParams<Events, Name>) => unknown;
type IOnce = boolean;
export type IEventEmitter<IEvents extends IEventMap> = {
on<
Name extends IEventNames<IEvents>,
Handler extends IEventHandler<IEvents, Name>
>(
name: Name,
handler: Handler
): IEventEmitter<IEvents>;
once<
Name extends IEventNames<IEvents>,
Handler extends IEventHandler<IEvents, Name>
>(
name: Name,
handler: Handler
): IEventEmitter<IEvents>;
off<
Name extends IEventNames<IEvents>,
Handler extends IEventHandler<IEvents, Name>
>(
name?: Name,
handler?: Handler
): IEventEmitter<IEvents>;
emit<
Name extends IEventNames<IEvents>,
Params extends IEventParams<IEvents, Name>
>(
name: Name,
...args: Params
): IEventEmitter<IEvents>;
};
export const createEmitter = <
Events extends IEventMap
>(): IEventEmitter<Events> => {
type EventNames = keyof Events;
type EventParams<Name extends EventNames> = Events[Name];
type EventHandler<EventName extends EventNames> = (
...args: EventParams<EventName>
) => unknown;
let listeners = new Map<EventNames, [EventHandler<any>, IOnce][]>();
const emitter: IEventEmitter<Events> = {
on(name, handler) {
if (listeners.has(name)) listeners.get(name)?.push([handler, false]);
else listeners.set(name, [[handler, false]]);
return emitter;
},
once(name, handler) {
if (listeners.has(name)) listeners.get(name)?.push([handler, false]);
else listeners.set(name, [[handler, false]]);
return emitter;
},
off(name, handler) {
if (!name) {
listeners = new Map<EventNames, [EventHandler<any>, IOnce][]>();
return emitter;
}
if (listeners.has(name)) {
if (handler) {
let pairs = listeners.get(name)!;
pairs = pairs.filter(([func]) => func === handler);
if (pairs.length) listeners.set(name, pairs);
else listeners.delete(name);
} else {
listeners.delete(name);
}
}
return emitter;
},
emit(name, ...args) {
let pairs = listeners.get(name);
if (pairs) {
pairs = pairs.filter(([handle, emitOnce]) => {
handle(...args);
return emitOnce === false;
});
if (pairs.length) listeners.set(name, pairs);
else listeners.delete(name);
}
return emitter;
},
};
return emitter;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment