Last active
February 9, 2021 21:06
-
-
Save frzi/400c7dc74e37a60d21bc5c3b73c5c739 to your computer and use it in GitHub Desktop.
Strongly typed Event Emitter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Custom written EventEmitter. | |
* Shares most of the same interface as Node.js' Event Emitter. | |
* Except everything is strongly typed and stored in `#listeners`. | |
*/ | |
type Arguments<T> = [T] extends [(...args: infer U) => any] ? U : [T] extends [void] ? [] : [T] | |
type Listener<T> = (...argv: Arguments<T>) => void | |
interface DefaultEvents { | |
[key: string]: (...values: unknown[]) => void | |
} | |
class EventDescription<T extends () => void> { | |
constructor( | |
public readonly fn: T, | |
public readonly once: boolean | |
) {} | |
} | |
export default class EventEmitter<Events = DefaultEvents> { | |
#listeners: { | |
[K in keyof Events]?: EventDescription<Listener<Events[K]>>[] | |
} = {} | |
// | |
// Add / remove / emit. | |
private addEvent<K extends keyof Events>(event: K, fn: Listener<Events[K]>, once: boolean): this { | |
this.#listeners[event] = this.#listeners[event] || [] | |
this.#listeners[event].push(new EventDescription(fn, once)) | |
return this | |
} | |
public on<K extends keyof Events>(event: K, fn: Listener<Events[K]>): this { | |
return this.addEvent(event, fn, false) | |
} | |
public once<K extends keyof Events>(event: K, fn: Listener<Events[K]>): this { | |
return this.addEvent(event, fn, true) | |
} | |
public off<K extends keyof Events>(event: K, fn: Listener<Events[K]>): this { | |
if (this.#listeners[event]) { | |
this.#listeners[event] = this.#listeners[event].filter(description => description.fn != fn) | |
if (this.#listeners[event].length == 0) { | |
delete this.#listeners[event] | |
} | |
} | |
return this | |
} | |
public removeAllListeners<K extends keyof Events>(event?: K): this { | |
if (event) { | |
delete this.#listeners[event] | |
} | |
else { | |
this.#listeners = {} | |
} | |
return this | |
} | |
public emit<K extends keyof Events>(event: K, ...argv: Arguments<Events[K]>): this { | |
if (this.#listeners[event]) { | |
this.#listeners[event] = this.#listeners[event].filter(description => { | |
description.fn.call(this, ...argv) | |
return !description.once | |
}) | |
} | |
return this | |
} | |
// | |
// Query etc. | |
public listeners<K extends keyof Events>(event: K): Listener<Events[K]>[] { | |
return this.#listeners[event]?.map(description => description.fn) || [] | |
} | |
public listenersCount<K extends keyof Events>(event: K): number { | |
return this.#listeners[event]?.length ?? 0 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment