Node Tuts

Detach script window ⇗

You can navigate the video and the script by using the ← and → cursor keys.

The Event Emitter Pattern

The Callback pattern — which I convered on the previous episode — is useful when you are issuing granular I/O simple operations either fail or succeed.

But if you have an object that is more complex, like a TCP connection, you may want to perform some actions when the connection is opened, when it is closed, when some data arrives and when there is an error.

This pattern is sometimes known as the pub-sub pattern where there is the event publisher and zero or many event subscribers.

The principles here are similar, but the nomenclature changes a bit: there is the event emitter part which generate events that can be listened by event listeners.

An Example first

Here is an example of a simple TCP Server:

var net = require('net');
var format = require('util').format;

var server = net.createServer();

server.on('connection', function(conn) {

  var printPrefix = '[' + conn.remoteAddress + ':' + conn.remotePort +  '] ';
  function print() {
    var formatted = format.apply({}, arguments);
    console.log(printPrefix + formatted);
  }

  print('connected');

  conn.on('data', function(data) {
    print('got some data:', data);
  });

  conn.on('end', function() {
    print('ended.');
  });

  conn.on('close', function() {
    print('closed.');
  });

  conn.on('error', function(err) {
    print('error:', err);
  });

  conn.setEncoding('utf-8');

});

server.on('error', console.error);

server.listen(8080);

Here we're using several event emitters.

First, the net.createServer returns a net.Server object that is an event emitter. Besides other event types, this object emits a connection event every time a client connects to it.

Here we're registering a listener for this event type on this object by using the .on(<event type>, <event listener>) method. This method binds the specified event type — connection in this case to the event listener — our callback function.

The callback function then may get some event-type-specific data as is the case of the conn argument, which is of type net.Socket. This connection object is also an event emitter on its own, and can emit, among others, data, close and end events.

Let's test this server now.

Event Types

The event type is represented by a string, usually lower-cased and containing only letters. Each event type passes arguments to the listeners, and these arguments are specific to the event type — some documentation should be provided about which arguments the listeners for this event type should expect.

Listener

The event listener is just a simple JavaScript function, either declared inline (like in the previous example) or declared in advance.

You can either declare it inline like this:

server.on('connection', function(conn) { ...

or name the function:

function handleConnection(conn) {
  //...
}

server.on('connection', handleConnection);

Also, you can have more than one event listener for the same event type on the same event emitter, it's just a matter of registering it using the .on() method.

Once

If some events will happen only once, at most once or if you're only interested in the first instance of that event, you should use .once instead of .on like we did on the socket close and end events:

conn.once('close', function() {
  print('closed.');
});

Removing Listeners

After it is registered, you can remove an event listener from the event emitter by calling .removeListener(<event type>, <event listener>). For this, you will have to name the functions like this:

function closeHandler() {
  //...
}

emitter.on('close', closeHandler);

// ...

emitter.removeListener('close', closeHandler);

The Special Case of Error

The event type is just a meaningless identifier that is used by the event emitter to dispatch events with the exception of the error event type.

If an event of type error occurs and the event emitter has listeners for it, everything goes as planned.

But if there are no event listeners for when an error event occurs, an uncaught exception is thrown into the event loop.

You can still catch that error if you register for the global uncaughtException event type emitted by the global process object like this:

process.on('uncaughtException', function(err) {
  console.error(err);
  process.exit();
});

The usage of the process uncaught exception handler is not advised for things other than logging since it's not contextualized and you may have left a series of resources unattended for — open connections, hanging requests, etc.

As a rule of thumb, always listen to the error event.