Write your own chainable Event Emitter class for Node and Browser
Tags: node, events, beginners
Published: 2018-12-08 10:24:03

---
title: Write your own chainable Event Emitter class for Node and Browser
published: true
description: It is saturday morning, I am still waiting for coffee so let's do something trivial to warm up for the day.
tags: node, events, beginners
cover_image: https://thepracticaldev.s3.amazonaws.com/i/hylgvoiby74gr298kg0o.gif
---

It is saturday morning, I am still waiting for coffee so let's do something trivial to warm up for the day.

## Write your own chainable Event Emitter class for Node and Browser

An event emitter in JavaScript generally has three methods.

### addEventListener
Add / register a listener that will be called when an event is dispatched.

### removeEventListener
Remove / unregister an event listener.

### dispatchEvent
This method is used to _trigger_ an event of a certain type.


## The class 
First of all we make sure Emitter is instanciated and not called as a function.

```js
function Emitter() {
    if (!(this instanceof Emitter)) throw new TypeError('Emitter is not a function.')
    // ...
}
```

Declare a private variable to store listeners. This array is going to be populated with more arrays where array[0] is the event type and array[1] the callback function.

```js
/**
 * Store event handlers here.
 * @type {Array}
 * @private
 */
let handlers = []
``` 

### addEventListener
This method will add/register a new event listener for events of the provided type by adding an item of type array to _handlers_ where array[0] is the type and array[1] the callback.

```js
/**
 * Add event listener.
 * @param {string} event type 
 * @param {function} callback function
 */
this.addEventListener = (type, fn) => {
    handlers.push([type, fn])
}
```

Once instanciated, you call addEventListener as follows:

```js
emitter.addEventListener('message', message => console.log('received a message!', message))
```

### removeEventListener
We also need to be able to remove event listeners we no longer need. To do this, we need to remove all items in _handlers_ where item[0] is the event type.

```js
/**
 * Remove event listener.
 * @param {string} event type 
 * @param {function} callback function
 */
this.removeEventListener = (type, fn = true) => {
    handlers = handlers.filter(handler => !(handler[0] == type && (fn == true ? true : handler[1] == fn)))
}
```

```js
emitter.addEventListener('ready', console.log) // console.log will be called when a ready event happens
emitter.removeEventListener('ready', console.log)  // console.log will no longer be called on ready events
```

### dispatchEvent
The method to trigger an event is called _dispatchEvent_ in the browser. In Node environments it is generally called _emit_.
We are going to slightly modify this function so that it supports wildcard event types (as seen in [https://www.npmjs.com/package/eventemitter2](EventEmitter2)). Additionally besides the event data, a a second argument _type_ is given to the event handler. When you implement wildcard event type support, this argument is useful to determine what's what.

```js
// without the type argument, this event could be anything
emitter.addEventListener('*', (event, type) => console.log(`an event of type = ${type} was emitted.`))
emitter.addEventListener('user:*', (event, type) => console.log(`something usery happened.`))
```

```js
/**
 * Dispatch event.
 * @param {string} event type 
 * @param {any} event data
 */
 this.dispatchEvent = (type, data) => {
    handlers.filter(handler => new RegExp("^" + handler[0].split("*").join(".*") + "$").test(type)).forEach(handler => handler[1](data, type))
 }
```
### getEventListeners
Perhaps you want to be able to get/list all event listeners (of a certain type). 

```js
/**
 * Get list of event handlers (of a type) or all if type is not specified
 * @param {string} [event type] (optional) 
 */
 this.getEventListeners = type => {
    if (!type)
        return handlers
    let fns = []
    handlers.filter(handler => handler[0] == type).forEach(handler => fns.push(handler[1]))

    return fns
}
```

### clearEventListeners
Let's also add this extra method that will clear all event listeners by reinitializing _handlers_.
```js
/**
 * Clear event listeners
 * @param {string} [event type] (optional)
 */
    this.clearEventListeners = () => { handlers = [] }
```

## So far
Our Emitter class now looks something like this.

```js
function Emitter() {
    if (!(this instanceof Emitter)) throw new TypeError('Emitter is not a function.')

    let handlers = []

    this.addEventListener = (type, fn) => {
        handlers.push([type, fn])
    }

    this.removeEventListener = (type, fn = true) => {
        handlers = handlers.filter(handler => !(handler[0] == type && (fn == true ? true : handler[1] == fn)))
    }

    this.dispatchEvent = (type, data) => {
        handlers.filter(handler => new RegExp("^" + handler[0].split("*").join(".*") + "$").test(type)).forEach(handler => handler[1](data, type))
    }

    this.clearEventListeners = () => { handlers = [] }

    this.getEventListeners = type => {
        if (!type)
            return handlers

        let fns = []
        handlers.filter(handler => handler[0] == type).forEach(handler => fns.push(handler[1]))

        return fns
    }
}
```

Congratulations! You have a working event emitter class. Try it yourself:

```js
var emitter = new Emitter()
emitter.addEventListener('ready', console.log)
emitter.addEventListener('foo.*', (event, type) => console.log({type,event}))
emitter.dispatchEvent('ready', Date.now())
emitter.dispatchEvent('foo.bar', 'blabalbla')
emitter.removeEventListener('ready', console.log)
emitter.clearEventListeners()
```

But we are not done, I promised a chainable event emitter. Chainable means that the Emitter is a singleton that always returns itself, allowing you to keep calling methods on it.

## Shortcuts
Because we don't like to write _addEventListener_ and _dispatchEvent_ all the time, let's add these shortcuts. These shortcuts all return _this_ at the end to make chains.

```js
 /**
     * Shortcut for addEventListener.
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.on = (type, fn) => {
        this.addEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for removeEventListener
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.off = (type, fn) => {
        this.removeEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for dispatchEvent
     * @param {string} event type 
     * @param {any} event data
     */
    this.emit = (type, data) => {
        this.dispatchEvent(type, data)
        return this /* chain */
    }
    
     /**
     * Shortcut for clearEventListeners
     * @param {string} event type 
     */
    this.clear = type => {
        this.clearEventListeners(type)
        return this
    }

    /**
     * 
     * @param {string} [type]
     */
    this.list = type => this.getEventListeners(type)
```

Now our Event Emitter class can be accessed like:

```js
emitter.on('message', message => console.log(message).on('open', onOpen).on('error', console.error).emit('ready', { status: 200, details: 'this is a ready event'})
```

## Final result: class Emitter
Your final Emitter class should look something like this:
```js
/**
 * Simpler EventTarget class without the need to dispatch Event instances.
 * @constructor
 * @returns {Emitter} new instance of Emitter
 */
function Emitter() {
    if (!(this instanceof Emitter)) throw new TypeError('Emitter is not a function.')

    /**
     * Store event handlers here.
     * @type {Array}
     * @private
     */
    let handlers = []

    /**
     * Add event listener.
     * @param {string} event type 
     * @param {function} callback function
     */
    this.addEventListener = (type, fn) => {
        handlers.push([type, fn])
    }

    /**
     * Remove event listener.
     * @param {string} event type 
     * @param {function} callback function
     */
    this.removeEventListener = (type, fn = true) => {
        handlers = handlers.filter(handler => !(handler[0] == type && (fn == true ? true : handler[1] == fn)))
    }

    /**
     * Dispatch event.
     * @param {string} event type 
     * @param {any} event data
     */
    this.dispatchEvent = (type, data) => {
        handlers.filter(handler => new RegExp("^" + handler[0].split("*").join(".*") + "$").test(type)).forEach(handler => handler[1](data, type))
    }

    /**
     * Clear event listeners
     * @param {string} [event type] (optional)
     */
    this.clearEventListeners = () => { handlers = [] }

    /**
     * Get list of event handlers (of a type) or all if type is not specified
     * @param {string} [event type] (optional) 
     */
    this.getEventListeners = type => {
        if (!type)
            return handlers

        let fns = []
        handlers.filter(handler => handler[0] == type).forEach(handler => fns.push(handler[1]))

        return fns
    }

    /**
     * Shortcut for addEventListener.
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.on = (type, fn) => {
        this.addEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for removeEventListener
     * @param {string} event type 
     * @param {function} callback function 
     */
    this.off = (type, fn) => {
        this.removeEventListener(type, fn)
        return this /* chain */
    }

    /**
     * Shortcut for dispatchEvent
     * @param {string} event type 
     * @param {any} event data
     */
    this.emit = (type, data) => {
        this.dispatchEvent(type, data)
        return this /* chain */
    }
    
     /**
     * Shortcut for clearEventListeners
     * @param {string} event type 
     */
    this.clear = type => {
        this.clearEventListeners(type)
        return this
    }

    /**
     * 
     * @param {string} [type]
     */
    this.list = type => this.getEventListeners(type)
}

module.exports = Emitter
```

## Done!
Good job, you have successfullly copy pasted my code!