Taking Baby Steps with Node.js – Implementing Events
Here are the links to the previous installments:
- Introduction
- Threads vs. Events
- Using Non-Standard Modules
- Debugging with node-inspector
- CommonJS and Creating Custom Modules
- Node Version Management with n
As I already mentioned in one of the previous posts, events lie at the heart of Node.js. In fact, events ARE the heart of Node.js. When building our own custom modules, we are able to make use of this functionality provided by Node.js for emitting our very own events. We can do this by using the EventEmitter exposed by the built-in ?events? module. The following code snippet demonstrates how to use the simple API of the EventEmitter.
var events = require('events'); var eventEmitter = new events.EventEmitter(); eventEmitter.on('someOccurence', function(message){ console.log(message); }); eventEmitter.emit('someOccurence', 'Something happened!');
After creating an eventEmitter object, we subscribe to an event named ?someOccurence? and register a function with a message argument to be called when the event is raised. Subscribing is done by executing the on() method of the eventEmitter object. Next we raise our event by simply calling the emit() method of the eventEmitter object, also passing in the value for the message argument of the event handler function.
When providing our own constructor functions exposed by a custom module, we typically want to inherit from EventEmitter. This is also the most common usage throughout the implementations of the built-in modules provided by Node.js. The following example shows how easy it is to turn a constructor function into an event emitter.
var sys = require('sys'), events = require('events'); function Downloader() { if(false === (this instanceof Downloader)) { return new Downloader(); } events.EventEmitter.call(this); } sys.inherits(Downloader, events.EventEmitter); Downloader.prototype.download = function(episode) { var self = this; var statusMessage = 'Downloading: ' + episode; self.emit('status', statusMessage); setTimeout(function() { var finishedMessage = 'Downloaded ' + episode; self.emit('finished', finishedMessage); }, 5000); } exports.Downloader = Downloader;
Here we created a constructor function named Downloader. Inside this constructor function we execute EventEmitter.call(this) which simply invokes the EventEmitter constructor function (re)using the current context. We then inherit from the EventEmitter by calling the inherits() helper method of the ?sys? module. The implementation of this method looks like this:
exports.inherits = function(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false } }); };
This helper method ensures that the prototype methods of the specified superCtor are inherited into ctor. By calling this method we add the prototype methods of the EventEmitter function to our own Downloader function so they can be used by external code.
We also added a download() method to the prototype of our constructor function that emits two different events named ?status? and ?finished?. The first event is raised immediately when the download() method is invoked. The second event is raised after five seconds.
The following code demonstrates how to use our Downloader which we exposed through a module named ?podcast?.
var podcast = require("./podcast"); var downloader = new podcast.Downloader(); downloader.on('status', function(message) { console.log(message); }); downloader.on('finished', function(message) { console.log(message); }); downloader.download('Astronomy podcast #89');
Here we create a downloader object, register for the two events that we exposed and call the download() method. Instead of adding our constructor function to the exports object, we can also choose to replace the entire reference that is hold by the exports property of the current module as shown in this previous post.
var sys = require('sys'), events = require('events'); function Podcast() { if(false === (this instanceof Podcast)) { return new Podcast(); } events.EventEmitter.call(this); } sys.inherits(Podcast, events.EventEmitter); Podcast.prototype.download = function(episode) { var self = this; var statusMessage = 'Downloading: ' + episode; self.emit('status', statusMessage); setTimeout(function() { var finishedMessage = 'Downloaded ' + episode; self.emit('finished', finishedMessage); }, 5000); } module.exports = Podcast;
Here we renamed Downloader to Podcast and assigned it to the exports property of our custom module. Now we can use it like so:
var Podcast = require("./podcast"); var podcast = new Podcast(); podcast.on('status', function(message) { console.log(message); }); podcast.on('finished', function(message) { console.log(message); }); podcast.download('Astronomy podcast #89');
This approach looks a bit nicer to me, but that?s just my humble opinion.
As I mentioned earlier, deriving from EventEmitter is a common approach used by many of the built-in modules as well as many of the third-party modules provided by the community. Take a look at the following code that uses the API?s exposed by the ?http? module for requesting a page on the web.
var http = require('http'); var url = require('url'); var parsedUrl = url.parse('http://www.google.com'); var client = http.createClient(80, parsedUrl.hostname); var request = client.request(parsedUrl.pathname, {'host': parsedUrl.hostname}); request.on('response', function(response) { console.log(response.headers); }); request.end();
This example simply retrieves the Google main page and prints out the headers to the console when a response is received. The dumbed down implementation of this API looks like this:
function Client() { // Further implementation ... } exports.Client = Client; exports.createClient = function(port, host) { var c = new Client(); return c; }; Client.prototype.request = function(method, url, headers) { var req = new ClientRequest(options); return req; }; function ClientRequest(options) { OutgoingMessage.call(this); // Further implementation ... } util.inherits(ClientRequest, OutgoingMessage); exports.ClientRequest = ClientRequest;
Here we basically see the same approach except that a number of factory methods are provided in order to easily create and set up the required objects.
Events are essential when building applications with Node.js. Being able to easily use and expose them in your own custom modules is one of the strengths of this great server-side framework.
Until next time.
Recent Comments