cla66ic/events.ts
2024-04-28 18:33:00 +03:00

232 lines
6.6 KiB
TypeScript

// deno-lint-ignore-file
/** The callback type. */
type Callback = (...args: any[]) => any | Promise<any>;
/** A listener type. */
type Listener = Callback & { __once__?: true };
/** The name of an event. */
type EventName = string | number;
type EventsType =
& { [key: string]: Callback }
& { [key: number]: Callback };
/**
* The event emitter.
*/
export class EventEmitter<E extends EventsType = {}> {
/**
* This is where the events and listeners are stored.
*/
private _events_: Map<keyof E, Set<Listener>> = new Map();
/**
* Listen for a typed event.
* @param event The typed event name to listen for.
* @param listener The typed listener function.
*/
public on<K extends keyof E>(event: K, listener: E[K]): this;
/**
* Listen for an event.
* @param event The event name to listen for.
* @param listener The listener function.
*/
public on(event: EventName, listener: Callback): this {
if (!this._events_.has(event)) this._events_.set(event, new Set());
this._events_.get(event)!.add(listener);
return this;
}
/**
* Listen for a typed event once.
* @param event The typed event name to listen for.
* @param listener The typed listener function.
*/
public once<K extends keyof E>(event: K, listener: E[K]): this;
/**
* Listen for an event once.
* @param event The event name to listen for.
* @param listener The listener function.
*/
public once(event: EventName, listener: Callback): this {
const l: Listener = listener;
l.__once__ = true;
return this.on(event, l as any);
}
/**
* Remove a specific listener in the event emitter on a specific
* typed event.
* @param event The typed event name.
* @param listener The typed event listener function.
*/
public off<K extends keyof E>(event: K, listener: E[K]): this;
/**
* Remove all listeners on a specific typed event.
* @param event The typed event name.
*/
public off<K extends keyof E>(event: K): this;
/**
* Remove all events from the event listener.
*/
public off(): this;
/**
* Remove a specific listener on a specific event if both `event`
* and `listener` is defined, or remove all listeners on a
* specific event if only `event` is defined, or lastly remove
* all listeners on every event if `event` is not defined.
* @param event The event name.
* @param listener The event listener function.
*/
public off(event?: EventName, listener?: Callback): this {
if (!event && listener) {
throw new Error("Why is there a listener defined here?");
} else if (!event && !listener) {
this._events_.clear();
} else if (event && !listener) {
this._events_.delete(event);
} else if (event && listener && this._events_.has(event)) {
const _ = this._events_.get(event)!;
_.delete(listener);
if (_.size === 0) this._events_.delete(event);
} else {
throw new Error("Unknown action!");
}
return this;
}
/**
* Emit a typed event without waiting for each listener to
* return.
* @param event The typed event name to emit.
* @param args The arguments to pass to the typed listeners.
*/
public emitSync<K extends keyof E>(event: K, ...args: Parameters<E[K]>): this;
/**
* Emit an event without waiting for each listener to return.
* @param event The event name to emit.
* @param args The arguments to pass to the listeners.
*/
public emitSync(event: EventName, ...args: Parameters<Callback>): this {
if (!this._events_.has(event)) return this;
const _ = this._events_.get(event)!;
for (let [, listener] of _.entries()) {
const r = listener(...args);
if (r instanceof Promise) r.catch(console.error);
if (listener.__once__) {
delete listener.__once__;
_.delete(listener);
}
}
if (_.size === 0) this._events_.delete(event);
return this;
}
/**
* Emit a typed event and wait for each typed listener to return.
* @param event The typed event name to emit.
* @param args The arguments to pass to the typed listeners.
*/
public async emit<K extends keyof E>(
event: K,
...args: Parameters<E[K]>
): Promise<any[]>;
/**
* Emit an event and wait for each listener to return.
* @param event The event name to emit.
* @param args The arguments to pass to the listeners.
*/
public async emit(
event: EventName,
...args: Parameters<Callback>
): Promise<any[]> {
let returns: any[] = [];
if (!this._events_.has(event)) return returns;
const _ = this._events_.get(event)!;
for (let [, listener] of _.entries()) {
try {
returns.push(await listener(...args));
if (listener.__once__) {
delete listener.__once__;
_.delete(listener);
}
} catch (error) {
console.error(error);
}
}
if (_.size === 0) this._events_.delete(event);
return returns;
}
/**
* The same as emitSync, but wait for each typed listener to
* return before calling the next typed listener.
* @param event The typed event name.
* @param args The arguments to pass to the typed listeners.
*/
public queue<K extends keyof E>(event: K, ...args: Parameters<E[K]>): this;
/**
* The same as emitSync, but wait for each listener to return
* before calling the next listener.
* @param event The event name.
* @param args The arguments to pass to the listeners.
*/
public queue(event: EventName, ...args: Parameters<Callback>): this {
(async () => await this.emit(event, ...args as any))().catch(console.error);
return this;
}
/**
* Wait for a typed event to be emitted and return the arguments.
* @param event The typed event name to wait for.
* @param timeout An optional amount of milliseconds to wait
* before throwing.
*/
public pull<K extends keyof E>(
event: K,
timeout?: number,
): Promise<Parameters<E[K]>>;
/**
* Wait for an event to be emitted and return the arguments.
* @param event The event name to wait for.
* @param timeout An optional amount of milliseconds to wait
* before throwing.
*/
public pull(
event: EventName,
timeout?: number,
): Promise<Parameters<Callback>> {
return new Promise(async (resolve, reject) => {
let timeoutId: Timer | null;
let listener = (...args: any[]) => {
if (timeoutId !== null) clearTimeout(timeoutId);
resolve(args);
};
timeoutId = typeof timeout !== "number"
? null
: setTimeout(() => (this.off(event, listener as any),
reject(
new Error("Timed out!"),
))
);
this.once(event, listener as any);
});
}
}
export default EventEmitter;