Skip to content

Instantly share code, notes, and snippets.

Last active October 27, 2020 22:51
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Flexible Event Emitter for TypeScript
* EventEmitter
* @desc An event emitter
interface Listener {
cb: (...args: any[]) => void
count: number
name: string
class EventEmitter {
listeners: Map<string, Listener[]> = new Map()
on (param1: Listener['name'] | Listener, param2?: Listener['cb'], param3?: Listener['count']) {
// Store reference to listener or create a listener from the params passed
const listenerObj = ( === '[object Object]' ? param1 : { name: param1, cb: param2, count: param3 || Infinity}) as Listener
// Register `` (if it hasn't been already) and then add the new listener to its list
this.listeners.set(, [...(this.listeners.get( || []), listenerObj])
// Return an object with a reference to `off` with `listenerObj` populated, and the listener itself
return {
listener: listenerObj,
off: () =>
off (listener: Listener) {
// Exit if `` isn't registered
if (!this.listeners.has( return
// Store reference to listeners registered to ``
let listenerRef = this.listeners.get(!
// Create a copy of ``'s registered listeners, with `listener` removed
let filteredListeners = listenerRef.filter(eventListener => listener !== eventListener)
// If there is at least one listener still registered, update the list with filtered copy from above
if (filteredListeners.length) this.listeners.set(, filteredListeners)
// Otherwise de-register ``
else this.listeners.delete(
emit (name: any, ...args: any) {
// Exit if `name` isn't registered
if (!this.listeners.has(name)) return
// Loop over all listeners registered to `name`
return this.listeners.get(name)!.map(listener => {
// Call `callback` and pass through all arguments
const val = listener.cb(...args)
// Decrement `count`
listener.count -= 1
// If the count is `0`, then call `off`
if (listener.count === 0)
// Return the result of `cb`
return val
This Event Emitter was built to support the various implementations I've seen over the years
Hopefully this allows you to interact with it the way you prefer
// Create the instance like so
const emitter = new EventEmitter()
// You can register listeners either Inline or Referenced, let's cover Inline first
// Register inline listeners
// Argument 1 is the name to listen for
// Argument 2 is the callback to fire
// Argument 3 (optional) is how many times the listener responds before being destroyed (Defaults to `Infinity`)
const L1 = emitter.on('TEST_LISTENER', (...args) => console.log(args))
const L2 = emitter.on('TEST_LISTENER', (...args) => console.log(args), 1) // Passing `1` as the third argument means the `cb` will be called once and then it will be removed
// Emit `TEST_LISTENER` twice
emitter.emit('TEST_LISTENER', 1, 'Hello World') // Will result in both `testListener` and `testListenerTwo` being called
emitter.emit('TEST_LISTENER', 2, 'Hello World') // Will result in `testListener` being called as `testListenerTwo` has already exceeded its call count and been deleted
// Deregistering inline listeners
// When you call `emitter.on` the return value is an object with the listener and a function that will deregister the listener
// Emit `TEST_LISTENER` a third time
emitter.emit('TEST_LISTENER', 3, 'Hello World') // Will do nothing as both `L1` and `L2` have been deregistered. `L1` because we manually deregistered it, and `L2` because it was deregistered after its first call
// Emitting an event with no listeners will do nothing
emitter.emit('NOT_EXIST', 1, 'Hello Real World')
// Now let's use a Referenced listener
// A referenced listener is an object that explicitly defines the `name`, `count` and `cb` values
const L3Ref = {
count: Infinity,
cb: (...args) => console.log(args)
// You register a Referenced listener by passing the object as the first and only parameter
const L3 = emitter.on(L3Ref)
// You emit to References listeners just like Inline ones
emitter.emit('PROD_LISTENER', 2, 'Hello Real World')
// Referenced listeners can be removed by passing the object to the emitter's `off` method (either `L3Ref` or `L3.listener`)
// OR by calling the `off` function returned by the `emitter.on` method // Using the emitter method with the Ref // Using the emitter method with the returned listener // Using the returned method (when calling `emitter.on`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment