Skip to content

Instantly share code, notes, and snippets.

@Gk0Wk
Created October 21, 2022 03:28
Show Gist options
  • Save Gk0Wk/f258529e5e58039b12cd3faa4c219238 to your computer and use it in GitHub Desktop.
Save Gk0Wk/f258529e5e58039b12cd3faa4c219238 to your computer and use it in GitHub Desktop.
A lightweight but useful Event Emitter wheel for Browser&NodeJS. Writing in TS.
export type EventType = string | symbol;
export type EventListener<P, T> = (payload: P, type: T) => void;
export type EventListenerSet<Events, Key extends keyof Events> = Set<
EventListener<Events[Key], Key>
>;
export type EventListenerSetMap<Events> = Map<
keyof Events | '*',
EventListenerSet<Events, keyof Events>
>;
export interface EventListnerRegister<Events> {
<Key extends keyof Events>(
type: Key | '*',
listener: EventListener<Events[Key], Key>,
): EventListnerSelfUnregister;
listeners: EventListenerSetMap<Events>;
}
export type WaitForEventFunction<Events> = <Key extends keyof Events>(
type: Key,
) => Promise<Events[Key]>;
export type EventListnerUnregister<Events> = <Key extends keyof Events>(
type?: Key | '*',
listener?: EventListener<Events[Key], Key>,
once?: boolean,
) => void;
export type EventListnerSelfUnregister = () => void;
export type EventListnerEmitter<Events> = <Key extends keyof Events>(
type: Key,
payload?: Events[Key],
) => void;
export interface Emitter<Events = Record<EventType, unknown>> {
on: EventListnerRegister<Events>;
once: EventListnerRegister<Events>;
waitFor: WaitForEventFunction<Events>;
off: EventListnerUnregister<Events>;
emit: EventListnerEmitter<Events>;
}
export const emitter = <
Events = Record<EventType, unknown>,
>(): Emitter<Events> => {
const onListners: EventListenerSetMap<Events> = new Map();
const onceListners: EventListenerSetMap<Events> = new Map();
const on: EventListnerRegister<Events> = ((type: any, listener: any) => {
if (!onListners.has(type)) {
onListners.set(type, new Set());
}
onListners.get(type)!.add(listener);
return () => {
const listenerSet = onListners.get(type);
if (listenerSet) {
listenerSet.delete(listener);
if (listenerSet.size === 0) {
onListners.delete(type);
}
}
};
}) as any;
Object.defineProperty(on, 'listeners', {
configurable: false,
get: () => onListners,
});
const once: EventListnerRegister<Events> = ((type: any, listener: any) => {
if (!onceListners.has(type)) {
onceListners.set(type, new Set());
}
onceListners.get(type)!.add(listener);
return () => {
const listenerSet = onceListners.get(type);
if (listenerSet) {
listenerSet.delete(listener);
if (listenerSet.size === 0) {
onceListners.delete(type);
}
}
};
}) as any;
Object.defineProperty(once, 'listeners', {
configurable: false,
get: () => onceListners,
});
const waitFor: WaitForEventFunction<Events> = type =>
new Promise(resolve => once(type, resolve));
const off: EventListnerUnregister<Events> = (
type,
listener: any,
once = false,
) => {
if (type === undefined) {
onListners.clear();
onceListners.clear();
} else if (listener === undefined) {
if (once) {
onceListners.delete(type);
} else {
onListners.delete(type);
}
} else {
const listeners = once ? onceListners : onListners;
const listenerSet = listeners.get(type);
if (listenerSet) {
listenerSet.delete(listener);
if (listenerSet.size === 0) {
listeners.delete(type);
}
}
}
};
const emit: EventListnerEmitter<Events> = (type, payload) => {
onListners
.get('*')
?.forEach(async listener => listener(payload as any, type));
onListners
.get(type)
?.forEach(async listener => listener(payload as any, type));
onceListners
.get('*')
?.forEach(async listener => listener(payload as any, type));
onceListners
.get(type)
?.forEach(async listener => listener(payload as any, type));
onceListners.delete('*');
onceListners.delete(type);
};
return {
on,
once,
waitFor,
off,
emit,
};
};
export default emitter;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment