Skip to content

Instantly share code, notes, and snippets.

@HKhademian
Created April 2, 2023 21:47
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 HKhademian/2ead6bf59b0ff43b418d22c41576be36 to your computer and use it in GitHub Desktop.
Save HKhademian/2ead6bf59b0ff43b418d22c41576be36 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bun
import { EventButs } from "./EventBu";
enum MyEvents1 {
Val1 = 'Val1',
Event1 = 'Event1',
Event2 = 'Event2',
Event3 = 'Event3',
};
interface MyEvents1Def {
[MyEvents1.Val1]: number;
[MyEvents1.Event1](arg1: string, arg2: number): void;
[MyEvents1.Event2](arg1: number, arg2: string): void;
[MyEvents1.Event3](arg1: HTMLElement): void;
};
const bus = EventButs.get<MyEvents1Def>();
bus.on(MyEvents1.Event1, (arg1, arg2) => {
console.log(arg1, arg2);
});
// pass
bus.emit(MyEvents1.Event1, 'hello', 1);
// error: string is not assignable to number
bus.emit(MyEvents1.Event2, 1 + "", 'hello');
// error: null is not assignable to HTMLElement
bus.emit(MyEvents1.Event3, null);
const CHANNELS: { [key: string]: EventButs<unknown> } = {};
/**
* EventBuTS is a simple event emitter that can be used to emit events to multiple listeners
*/
export class EventButs<EventDef> {
private eventListeners: { [key in keyof Methods<EventDef>]?: (Methods<EventDef>[key])[] } = {};
private constructor() { }
static get<EventDef = unknown>(channelName?: string): EventButs<EventDef> {
channelName = channelName && channelName.length > 0 ? channelName.toLocaleLowerCase() : '';
return (CHANNELS[channelName] as EventButs<EventDef>)
|| (CHANNELS[channelName] = new this<EventDef>());
};
/**
* add a listener
* @param eventName name the event to add the listener to
* @param callback the callback to add
*/
public on<EventName extends keyof Methods<EventDef>>(
eventName: EventName,
callback: Methods<EventDef>[EventName],
) {
const eventListeners: Methods<EventDef>[EventName][] = this.eventListeners[eventName] || (this.eventListeners[eventName] = []);
eventListeners.push(callback);
return () => this.off(eventName, callback);
}
/**
* remove a listener
* @param eventName name the event to remove the listener from
* @param callback the callback to remove
*/
public off<EventName extends keyof Methods<EventDef>>(
eventName: EventName,
callback: Methods<EventDef>[EventName],
): void {
const eventListeners = this.eventListeners[eventName] || [];
let index;
while ((index = eventListeners.indexOf(callback)) >= 0) {
eventListeners.splice(index, 1);
}
}
/**
* emit an event, it will execute in the next tick
* FIFO is garanteed, both for the listeners and events
* @param eventName name the event to emit to all listeners
* @param args arguments to pass to the listeners
*/
public emit<EventName extends keyof Methods<EventDef>>(
eventName: EventName,
...args: Params<EventDef, EventName>
): void {
setTimeout(() => {
this.emitNow(eventName, ...args);
}, 0);
}
/**
* immidiately emit an event and catch all errors
* @param eventName name the event to emit to all listeners
* @param args arguments to pass to the listeners
*/
public emitNow<EventName extends keyof Methods<EventDef>>(
eventName: EventName,
...args: Params<EventDef, EventName>
): void {
const eventListeners: Methods<EventDef>[EventName][] = this.eventListeners[eventName] || [];
for (const callback of eventListeners) {
try {
callback(...args);
}
catch (e) {
console.error(e);
}
}
}
}
/**
* Get all method keys of a type
*/
type _MethodKeys<T> = ({ [P in keyof T]: T[P] extends ((...args: any[]) => any) ? P : never })[keyof T];
type Methods<T> = { [P in _MethodKeys<T>]: T[P] extends ((...args: any[]) => any) ? T[P] : never };
type Params<T, K extends keyof Methods<T>> = Parameters<Methods<T>[K]>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment