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
.