How to write a custom event system in JavaScript
Published: 2021-11-19 07:18:01

When building complex applications it's always a good idea to take a step back and think through architectural choices from the get-go. One pattern that really forces us to do that is an event system or the closely related PubSub pattern, which we'll take a closer look at in this article.

The biggest advantage of this pattern is that we get to decouple parts of our application and have them talk to each other through a middleman, or in simpler cases, we just broadcast an event and have a listener that executes when the event is triggered. A big advantage of this pattern is that all our events get routed through *the same* logic before being picked up by the listeners, which allows us to do some extra work and extend all events in our system later on.

Let's clear up some terms before we move into code. Given a simple example like on-page notifications, our core functionality that will be executed can be a simple `alert()` . Now to not call it directly but actually use an event system, we need to implement an `Observer` that will listen to incoming events, delegate our invocation and call all the right functions for us. 

```jsx
const Observer = function() {
    var self = this
    self.events = {}

    return {

        dispatch: function(eventName) {
            // We already know we want to be able to call our observer and
            // it will need to dispatch the actual event
        },

        on: function(eventName, callback) {
            // Our observer will need to be able to listen itself for incoming events
            // that we'll notify the listeners about (via callbacks)
        }

    };

}();
```

This Observer pattern above will be our middleman and listen to events when we define them in like this: `Observer.on('eventName', callback)`. At the same time, it allows us to trigger those events via `Observer.dispatch('eventName')` whenever we want the receivers to be notified. The most important part inside our observer is the `events` object  that will hold all callbacks in the form of `{[eventName: callback()]}`. Let's go ahead and fill the functions with code to see how the Observer pattern works in action.

## Implementing the observer pattern

```jsx
const Observer = function() {
    var self = this
    self.events = {}

    return {

        dispatch: function(eventName) {
            var events = self.events[eventName]
            events.forEach((callback) => callback())
        },

        on: function(eventName, callback) {
            if (typeof self.events[eventName] === 'undefined') {
                self.events[eventName] = []
            }

            self.events[eventName].push(callback)
        }

    }
}()
```

The `on` method is used to register our events and we need to call it before we can actually dispatch an event. it takes an event name and a callback as parameters and pushes both into our events object as described above.

Our `dispatch` function that will be triggered to fire off an event, takes an event name as the only argument. In fact, this should be the same event name that we previously added to the events object when we registered the event with our `on` function.

And with that, we have a working event system that can be used like this:

```jsx
// Listen to an event
Observer.on('testevent', function() {
    console.log('This will be executed when our event fires')
})

// Fire our event
Observer.dispatch('testevent')
```

Keep in mind that this is a very minimal custom event system and there are more mature systems out there. If we were to expand this minimal example, we'd need to take proper care of error handling, unbinding events (`off` function to our `on`), and implement some general checks around passed arguments.
   
If you want to dive deeper into the observer pattern or Pub Sub, here's a great resource for further reading with implementation details: [https://addyosmani.com/resources/essentialjsdesignpatterns/book/#mediatorpatternjavascript](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#mediatorpatternjavascript)