Last active
December 29, 2020 06:49
-
-
Save safareli/014fb65903d796ac57779de651eb8b06 to your computer and use it in GitHub Desktop.
typed TypeScript event emitter
This file contains 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
type Listener<T> = [T] extends [void] ? () => void : (payload: T) => void; | |
type BaseSpec = Record<string, unknown>; | |
type ListenerMap<T extends BaseSpec> = { [K in keyof T]?: Listener<T[K]>[] }; | |
type EventPayload<T> = [T] extends [void] ? [] : [payload: T]; | |
/** Typed event emitter implementation | |
* example: | |
* ```ts | |
* const myEventEmitter = new EventEmitter<{ | |
* foo: string[]; | |
* baz: void; | |
* }>(); | |
* | |
* | |
* myEventEmitter.emit("baz") | |
* myEventEmitter.on("foo", ________ ) | |
* // on(key: "foo", listener: Listener<string[]>): () => void | |
* | |
* myEventEmitter.emit("foo", _____ ) | |
* // emit(key: "foo", event: string[]): void | |
* | |
* ``` | |
*/ | |
export class EventEmitter<T extends BaseSpec> { | |
private listeners: ListenerMap<T> = {}; | |
/** | |
* Appends an event `listener` for events associated with `eventKey`, returns | |
* cleanup function which removes the listener. | |
*/ | |
on<K extends Extract<keyof T, string>>( | |
eventKey: K, | |
listener: Listener<T[K]> | |
): () => void { | |
const listeners = this.listeners[eventKey]; | |
if (listeners === undefined) { | |
this.listeners[eventKey] = [listener]; | |
} else { | |
listeners.push(listener); | |
} | |
return () => this.off(eventKey, listener); | |
} | |
/** | |
* Remove the event `listener` attached for events associated with `eventKey` | |
*/ | |
off<K extends Extract<keyof T, string>>( | |
eventKey: K, | |
listener: Listener<T[K]> | |
) { | |
const listeners = this.listeners[eventKey]; | |
if (listeners === undefined) return; | |
const callbackIndex = listeners.indexOf(listener); | |
if (callbackIndex > -1) listeners.splice(callbackIndex, 1); | |
} | |
/** | |
* Emit `event` associated with `eventKey` | |
*/ | |
emit<K extends Extract<keyof T, string>>( | |
eventKey: K, | |
...args: EventPayload<T[K]> | |
) { | |
this.listeners[eventKey]?.forEach((listener) => | |
listener( | |
// Ideally we wouldn't need this cast. Related issue: | |
// https://github.com/microsoft/TypeScript/issues/42139 | |
args[0] as T[K] | |
) | |
); | |
} | |
} | |
// Listener<T> | |
export class EventEmitter2<T> { | |
private multiEventEmitter: EventEmitter<{ event: T }> = new EventEmitter(); | |
/** | |
* Appends an event `listener` for events associated with `eventKey`, returns | |
* cleanup function which removes the listener. | |
*/ | |
on(listener: Listener<T>): () => void { | |
return this.multiEventEmitter.on("event", listener); | |
} | |
/** | |
* Remove the event `listener` attached for events associated with `eventKey` | |
*/ | |
off(listener: Listener<T>) { | |
this.multiEventEmitter.off("event", listener); | |
} | |
/** | |
* Emit `event` associated with `eventKey` | |
*/ | |
emit(...args: EventPayload<T>) { | |
this.multiEventEmitter.emit("event", ...args); | |
} | |
} | |
new EventEmitter2<void>().emit(); | |
new EventEmitter2<undefined>().emit(); | |
new EventEmitter2<null>().emit(null); | |
new EventEmitter2<boolean>().on((payload: boolean) => { | |
payload; | |
}); | |
new EventEmitter2<boolean>().emit(true); |
This file contains 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
export class Emitter<T> { | |
private emitter = new Event.EventEmitter(); | |
/** | |
* Appends an event `listener` for emitted events, returns | |
* cleanup function which removes the listener. | |
*/ | |
on(listener: (event: T) => void): () => void { | |
this.emitter.on("event", listener); | |
return () => this.off(listener); | |
} | |
/** | |
* Remove the already attached event `listener`. | |
*/ | |
off(listener: (event: T) => void) { | |
this.emitter.off("event", listener); | |
} | |
/** | |
* Emit the `event` | |
*/ | |
emit(event: T) { | |
this.emitter.emit("event", event); | |
} | |
} | |
export class EventEmitter<T extends Record<string, unknown>> { | |
private emitter = new Event.EventEmitter(); | |
/** | |
* Appends an event `listener` for events associated with `eventKey`, | |
* returns cleanup function which removes the listener. | |
*/ | |
on<K extends Extract<keyof T, string>>( | |
eventKey: K, | |
listener: (event: T[K]) => void | |
): () => void { | |
this.emitter.on(eventKey, listener); | |
return () => this.off(eventKey, listener); | |
} | |
/** | |
* Remove the event `listener` attached for events associated with `eventKey` | |
*/ | |
off<K extends Extract<keyof T, string>>( | |
eventKey: K, | |
listener: (event: T[K]) => void | |
) { | |
this.emitter.off(eventKey, listener); | |
} | |
/** | |
* Emit `event` associated with `eventKey` | |
*/ | |
emit<K extends Extract<keyof T, string>>(eventKey: K, event: T[K]) { | |
this.emitter.emit(eventKey, event); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment