Using Event Emitter in Node.js


5 minute read


a stream a “stream” of events, being emitted from a waterfall… eh? ¯\(ツ)

tl;dr: In this post we will look at using the Event Emitter module in node.js and how we might use it to create our own event-emitting programs. The repo with some sample code can be found at https://github.com/markthethomas/eventEmitterFun

If you’ve worked with client-side JavaScript, you will be familiar with the notion of events. Or, at the very least, I really really hope you are — JavaScript isn’t much fun without one of its core concepts/principles.

JavaScript’s event-driven nature is perfect for browser (and many server!) environments and applications. For one, people use browsers, so you have to deal with (at least) two key factors: 1) relatively unpredictable usage patterns and 2) low patience threshold.

Browser events let you respond to actions by a user in a timely and efficient way that won’t block everything else up in the browser. They let you respond to a variety of actions asynchronously in more declarative way. In other words, you decide how the program should flow based on an event, not the other way around. Ordering isn’t determined and you have to be ready for events in the order [B, C, D, A] as well as [A, B, C, D]. Cool, right? One alternative is specifying endlessly-long if-else branching logic or other maddening solutions — shiver.

Enough about events, though. They’re a big topic and I can’t do it justice here. The main thing we want to move to is events in node.js. In the browser, events get triggered by the host environment when something happens (click, scroll, page load, &c.). But what about in node, where we do not have any DOM or browser to send off scroll events?

That’s where the events module comes in. It gives us essentially the same concept of events in the browser, but for node.js. Many of the native modules in node use events pretty often.

For instance: a net.Server object emits an event each time a peer connects to it; a fs.ReadStream emits an event when the file is opened; a stream emits an event whenever data is available to be read.1

So, let’s dig in and use it! We’ll be sub-classing and/or extending the events module to create a simple event emitter of our own. You can use the events module to make almost anything you want into an emitter — servers, streams, and more all do that really frequently. Our simple example will just add a few methods to the module so we can use it like a bare-bones pub/sub-ish tool.

First, the tests, so we know when we’re done and what we want it to do:

Les Tests

"use strict";
import test from "ava";

const Emitter = require("../emitter");
const Events = new Emitter();

test("it should be an object", (t) => {
    t.is(typeof Events, "object");
});

test("can register a callback", (t) => {
    t.is(typeof Events, "object");
});

test("can register a callback with a scope", (t) => {
    Events.on(
        "foo",
        function () {
            return "bar";
        },
        this
    );
});

test("can trigger an event", (t) => {
    var bar = 1;

    Events.on("foo", function () {
        bar = 2;
    });

    Events.trigger("foo");

    t.is(bar, 2);
});

test("can trigger an event with arguments", (t) => {
    var bar = 1;

    Events.on("foo", function (v) {
        bar = v;
    });

    Events.trigger("foo", 5);

    t.is(bar, 5);
});

test("can trigger multiple callbacks on an event", (t) => {
    (function () {
        var baz = "baz";

        Events.on("foo", function () {
            t.is(baz, "bar");
        });

        baz = "bar";
    })();

    var bar = 1;
    Events.on("foo", function () {
        bar = 2;
    });

    Events.trigger("foo");
    t.is(bar, 2);
});

test("can remove callbacks from an event", (t) => {
    var bar = 1;

    Events.on("foo", function () {
        bar += 1;
    });

    Events.trigger("foo");
    Events.off("foo");
    Events.trigger("foo");

    t.is(bar, 2);
});

test("can remove specific callbacks from an event", (t) => {
    var bar = 1;

    var adder = function (v) {
        bar += v;
    };

    var multiplier = function (v) {
        bar *= v;
    };

    Events.on("foo", adder);
    Events.trigger("foo", 1);
    t.is(bar, 2);

    Events.on("foo", multiplier);
    Events.off("foo", adder);
    Events.trigger("foo", 100);

    t.is(bar, 200);
});

Le Code

And now, to work we go! We can use the ES6 extends syntax here to set up EventEmitter as the prototype link for our new object. Remember that even though JavaScript now has the class keyword, it’s just syntactic sugar. JavaScript still uses prototypes, not actual classes. People tend to have really strong opinions one way or the other about this topic, so I won’t go too deeply into it except to say that I actually like the simplicity of the syntax while knowing that prototypal delegation is still what goes on under the hood. cue outrage!

I’ve also created a more ES5-friendly version in the repo.

Let’s add a couple methods that extend eventEmitter:

"use strict";

const EventEmitter = require("events");

class Emitter extends EventEmitter {
    off(evt, cb) {
        if (!cb) {
            return this.removeAllListeners(evt);
        }
        return this.removeListener(evt, cb);
    }

    trigger(evt) {
        let args = [].slice.call(arguments, 0);
        return this.emit.apply(this, args);
    }
}

module.exports = (function () {
    return new Emitter();
})();

Fin

Huzzah! Remember that you can use the events module to make pretty much whatever you want emit events. It can be as easy as firing this.emit.apply(this, args) with whatever data you want when, say, a user connects, data is fetched, or almost anything else. Go forth and event!

asciicast

Related: