Sat 16 April 2011

Emitting Custom Events in Node.js

Note the Following!

This is an article I wrote for the March 2011th issue of (the now defunct) JSMag. It was a great piece of literature released monthly, and a great way to keep up to date on the latest news in the Javascript community. Sad to see it go!

Node isn’t the first approach to event based programming, and with its explosion of interest it probably won’t be the last. Typical JavaScript patterns for callback functions involve passing around references to functions and managing odd scope levels. In many cases this is less than ideal; that said, there’s another option when you’re in Node: emit your own events, and let functions attach and respond to those. EventEmitter makes this incredibly easy!

The Typical Approach...

If you’ve written or even worked with JavaScript libraries before, you probably understand the callback function scenario – that is, functions that execute once a certain task is completed. A typical use might be something like what you see in the following example:

var x = 1;
var foo = function(callbackfn) {
    return callbackfn(x * 2);
};

foo(function(x) {
    console.log(x);
});

Here, we’ve defined a function that accepts another function as its main argument and passes the callback function a doubled version of x. Pretty simple, and many libraries use this technique for Ajax calls. Let’s take a minute and spin the globe, though – what if, instead of arbitrarily accepting a function and having to worry about possible scoping issues, we could just announce when an event of interest has occurred, and fire an attached function at that point? This would be so much cleaner than passing around function references everywhere.

Enter: events.EventEmitter

The great thing about all this? We can actually do this in Node through use of the events library. This, in many ways, is core to how things in Node work. Everything is event based, so why shouldn’t we be able to fire off our own events? To showcase what’s possible with this, let’s build a basic library to connect to Twitter's Streaming API, which we can then filter results from as we see fit.

The Basics: exporting an EventEmitter instance

Before we get into anything Twitter-specific, we’ll demonstrate basic usage of EventEmitter. The code below shows how simple this can really be – it’s a contrived example that constantly increases numbers by one, and emits an event called “even” every time the number becomes even.

var events = require('events'),
    util = require('util');

var Foo = function(initial_no) { this.count = initial_no; };

Foo.prototype = new events.EventEmitter;

Foo.prototype.increment = function() {
    var self = this;
    setInterval(function() {
        if(self.count % 2 === 0) self.emit('even');
        self.count++;
    }, 300);
};

var lol = new Foo(1);

lol.on('even', function() { 
    util.puts('Number is even! :: ' + this.count);
}).increment();

Usage of EventEmitter is pretty simple – you basically want to inherit all the properties from EventEmitter itself into your object, giving it all the properties it needs to emit events on its own. Events are sent off as keywords (‘even’, ‘error’, etc), called directly on the object. You can extend the prototype chain further, and EventEmitter should work fine and dandy.

Changing Tracks for a Moment...

Now that we’ve shown how EventEmitter works, we want to go ahead and use it for Twitter's Streaming API. For the unfamiliar, the Streaming API is essentially a never ending flood of tweets. You open a connection, and you keep it open; data is pushed to you, reversing the typical model of “request/response” a bit in that you only really make one request. EventEmitter is perfect for this task, but to satisfy some basic needs for interacting with Twitter's API, we’ll need a base library, like what’s shown in the example below:

var util = require('util'),
    http = require('http'),
    events = require('events');

var TwitterStream = function(opts) {
    this.username = opts.username;
    this.password = opts.password;
    this.track = opts.track;
    this.data = '';
};

TwitterStream.prototype = new events.EventEmitter;
module.exports = TwitterStream;

Here we require the three main resources we’ll need (util, http and events), and set up a new Function Object that’s essentially an instance of EventEmitter. We’ll throw it over to exports, too, so it plays nicely when relying on it in outside code. Creating instances of our Twitter object requires a few things – ‘track’, which is a keyword to filter tweets by, and a ‘username’/’password’ combination which should be self explanatory (in terms of what they are).

Why ‘username/password’, though? Twitter's Streaming API requires some form of authentication; for the sake of brevity in this article, we’re going to rely on Basic Authentication, but moving forward it’s recommended that you use OAuth for authenticating with Twitter, as it relies on the user granting you privileges instead of actually handing over their password. The OAuth ritual is much longer and more intricate to pull off, though, and would push the length and scope of this article far beyond its intentions.

Emitting a "Tweet" Event

Now that we’ve got the basic scaffolding for our library set up, let’s throw in a function to actually connect, receive tweets, and emit an event or two that other code can catch. Check out the following for a prime example of how we can do this:

TwitterStream.prototype.getTweets = function() {
    var opts = {
        host: 'stream.twitter.com',
        port: 80,
        path: '/1/statuses/filter.json?track=' + this.track,
        method: 'POST',
        headers: {
            'Connection': 'keep-alive',
            'Accept': '*/*',
            'User-Agent': 'Example Twitter Streaming Client',
            'Authorization': 'Basic ' + new Buffer(this.username + ':' + this.password).toString('base64'),
        }
    },
    self = this;

    this.connection = http.request(opts, function(response) {
        response.setEncoding('utf8');
        response.on('data', function(chunk) {
            self.data += chunk.toString('utf8');
            var index, json;
            while((index = self.data.indexOf('\r\n')) > -1) {
                json = self.data.slice(0, index);
                self.data = self.data.slice(index + 2);
                if(json.length > 0) {
                    try {
                        self.emit('tweet', JSON.parse(json));
                    } catch(e) {
                        self.emit('error', e);
                    }
                }
            }
        });
    });

    this.connection.write('?track=' + this.track);
    this.connection.end();
};

If you’ve worked with Node before, this code shouldn’t be too daunting, but we’ll summarize it just in case. We’re extending the prototype of our Twitter object that we created before, and adding a method to start the stream of tweets coming in. We set up an object detailing the host, port, path and method, as well as some custom headers (notably, setting ‘keep-alive’ and Basic Authentication headers). This is passed to an http.request() call, and we then write our tracking data and end the connection.

The response function has some logic to handle putting together tweets that are sent in by Twitter. The API dictates that a tweet object will end on the two characters ‘\\r’ and ‘\\n’, so we basically walk the built up JSON strings as they come in and separate them out. If a JSON string is successfully pulled out, we emit a ‘tweet’ event, and pass it the parsed JSON data. If something went horribly wrong, we emit an ‘error’ event and pass it the associated object.

Usage and Application

Alright, so now we should have a pretty functional library once we put those two together. The code below shows how we can now use this library in a simple script.

var TwitterStream = require('./twitterstream'),
    util = require('util');

var twitter = new TwitterStream({
    username: 'username',
    password: 'password',
    track: 'JavaScript',
});

twitter.on('tweet', function(tweet) {
    util.puts(util.inspect(tweet));
});

twitter.on('error', function(e) { 
    util.puts(e); 
});

twitter.getTweets();

Wrapping Things Up

EventEmitter is an excellent, easy to implement option for dealing with cases where you might want to defer an action until data is ready. Readers with further questions should check out the Node.js documentation on EventEmitter.

Ryan around the Web