So, since I've been swamped with work recently and haven't posted much, I'd like to get back into the swing of things and throw up a simple EventEmitter implementation I put together for my current ngReflux project I'm working on.

ngReflux is a Flux implementation for use with AngularJS applications and based on Mikael Brassman's original gist idea for RefluxJS. You can read more about the Flux architecture elsewhere.

Since Reflux removes the Dispatcher from the Flux flow altogether, it makes both Actions and Stores observables by having them prototypically inherit from an EventEmitter. Reflux uses EventEmitter3, which is a full implementation for Node and the browser; but I wanted to keep this small and simple, with few outside dependencies, so I rolled my own.

The Observer Pattern

Before we look at the code, let's quickly review the Observer Pattern, which is what EventEmitter is based on.

The Observer Pattern is fairly simple. An object (typically called a "subject") allows other Objects called Observers to be notified when some state in the Observable changes. This is essentially the pub/sub architecture.

Making an object "observable" focuses primarily on providing at least three different behaviors:

  1. addListener(label,callback) - Allow others to "listen" to the observer, providing it a callback to invoke when the observable's state changes.
  2. removeListener(label, callback) - Allow those listening to remove themselves as listeners so they can stop receiving notifications of state changes.
  3. emit(label,...) - Allow the observable to notify all observers of a state change by invoking each observer's callback with any changed state.

In our EventEmitter implementation we also have labels, which allows an object acting as an observable to allow others to listen for changes on a specific "channel", which is just a string identifying the type or category of changes the observer wants to be notified about. Observables can have one or more channels depending on their needs.

Implementing EventEmitter

Let's use some of the latest Javascript ES2015/ECMA Script 6 features and get this thing rolling.

First, we'll setup our EventEmitter as a class, which is really just syntactic sugar for creating a function that is intended to be invoked with new, combined with adding methods on its prototype property.

class EventEmitter {  
  constructor() {
    this.listeners = new Map();
  }
  addListener(label, callback) { }
  removeListener(label, callback) { }
  emit(label, ...args) {  }
}

Our class has a constructor which sets up an initially empty map for keeping track of our listeners. Note, too, the shorter syntax of declaring prototype methods on the class. We've stubbed them out above and will show the implementation below.

addListener(label, callback)

Adding a listener takes two parameters. We'll need the label that identifies the type of notifications the listener wants to receive; and the callback function we should invoke for the listener when we emit that event.

  addListener(label, callback) {
    this.listeners.has(label) || this.listeners.set(label, []);
    this.listeners.get(label).push(callback);
  }

We just ensure we have a queue (array of) of listeners for the given label, creating it if it isn't there; and then push the callback function into that queue.

removeListener(label, callback)

Removing a listener takes the same parameters as addListener; but we need to be able to find that callback in the appropriate list in order to remove it.

let isFunction = function(obj) {  
    return typeof obj == 'function' || false;
};

removeListener(label, callback) {  
    let listeners = this.listeners.get(label),
        index;

    if (listeners && listeners.length) {
        index = listeners.reduce((i, listener, index) => {
            return (isFunction(listener) && listener === callback) ?
                i = index :
                i;
        }, -1);

        if (index > -1) {
            listeners.splice(index, 1);
            this.listeners.set(label, listeners);
            return true;
        }
    }
    return false;
}

Outside of our helper function to determine if an object is a function, the implementation is straight forward. Loop through the listeners for that label, if there are any, and retain the index of the callback that matches the one passed in, using that to remove that item from the list.

If you aren't familiar with using Array functions like reduce and map, take a look at my previous post on the subject.

emit(label, ...args)

Here's where we emit events to all our listeners passing on some data that we think might interest them. We just need the label to emit the event on, which tells us which set of listeners to notify; and the remaining arguments are passed to each listener callback directly.

emit(label, ...args) {  
    let listeners = this.listeners.get(label);

    if (listeners && listeners.length) {
        listeners.forEach((listener) => {
            listener(...args); 
        });
        return true;
    }
    return false;
}

We use ES6's spread/rest operator here to collect all our arguments for emit into the single variable args; as well as to pass them as individual arguments to each listener callback invocation. Every time we emit on a label, all the other data we pass in gets passed to all our listeners callbacks.

Handling the arguments for each of those actions above in ES5 would be analogous to the following:

function emit(label) {  
  var args = [].slice.call(arguments, 1);
  // ...
          listeners.apply(null, args);
}

The spread/rest operator helps make for cleaner and clearer code in this case.

Putting it all together

Now that we have our EventEmitter class let's create an an observer and see how it works. Our simple Observer constructor takes a single argument of the specific Observable that we want to listen on. We then listen for "change" events and output any data received to the console.

class Observer {  
  constructor(id, subject) {
    this.id = id;
    this.subject = subject;
    this.subject.addListener("change", (data) => this.onChange(data));
  }
  onChange(data) {
    console.log(`${this.id} notified of change:`, data);
  }
}

We use ES6's new string templating interpolation to format our console output as well, which is pretty nice.

Now we can create an Observable using our EventEmitter and a couple of Observers and fire off some events and see it all work.

let observable = new EventEmitter();  
let [observer1, observer2] = [  
  new Observer(1, observable),
  new Observer(2, observable)
];

observable.emit("change", { a: 1 });  
// => (1) notified of change: { a: 1 }
// => (2) notified of change: { a: 1 }

We also used ES6's destructuring for the assignment on our observers, too.

Hopefully this has helped give you some insight into both the Observer Pattern and how EventEmitters are implemented. You can find out more about ES2015 and ES6 through Babel's learning resource. You can view the full code in a gist; and feel free to ask questions and leave comments!

Updated As commenters Greg and Christian pointed out, the better way to compare functions in the removeListener() method is to use straight === comparison rather than relying on .toString() to compare a decompiled version of the function. Using toString()'s results is inconsitent across various browsers/devices, and will not work for bound functions, using .bind().