Chapter 4. Core APIs

There are a lot of APIs in Node, but some of them are more important than others. These core APIs will form the backbone of any Node app, and you’ll find yourself using them again and again.

Events

The first API we are going to look at is the Events API. This is because, while abstract, it is a fundamental piece of making every other API work. By having a good grip on this API, you’ll be able to use all the other APIs effectively.

If you’ve ever programmed JavaScript in the browser, you’ll have used events before. However, the event model used in the browser comes from the DOM rather than JavaScript itself, and a lot of the concepts in the DOM don’t necessarily make sense out of that context. Let’s look at the DOM model of events and compare it to the implementation in Node.

The DOM has a user-driven event model based on user interaction, with a set of interface elements arranged in a tree structure (HTML, XML, etc.). This means that when a user interacts with a particular part of the interface, there is an event and a context, which is the HTML/XML element on which the click or other activity took place. That context has a parent and potentially children. Because the context is within a tree, the model includes the concepts of bubbling and capturing, which allow elements either up or down the tree to receive the event that was called.

For example, in an HTML list, a click event on an <li> can be captured by a listener on the <ul> that is its parent. Conversely, a click on the <ul> can be bubbled down to a listener on the <li>. Because JavaScript objects don’t have this kind of tree structure, the model in Node is much simpler.

EventEmitter

Because the event model is tied to the DOM in browsers, Node created the EventEmitter class to provide some basic event functionality. All event functionality in Node revolves around EventEmitter because it is also designed to be an interface class for other classes to extend. It would be unusual to call an EventEmitter instance directly.

EventEmitter has a handful of methods, the main two being on and emit. The class provides these methods for use by other classes. The on method creates an event listener for an event, as shown in Example 4-1.

Example 4-1. Listening for an event with the on method

server.on('event', function(a, b, c) {
  //do things
});

The on method takes two parameters: the name of the event to listen for and the function to call when that event is emitted. Because EventEmitter is an interface pseudoclass, the class that inherits from EventEmitter is expected to be invoked with the new keyword. Let’s look at Example 4-2 to see how we create a new class as a listener.

Example 4-2. Creating a new class that supports events with EventEmitter

var utils = require('utils'),
    EventEmitter = require('events').EventEmitter;

var Server = function() {
  console.log('init');
};

utils.inherits(Server, EventEmitter);

var s = new Server();

s.on('abc', function() {
  console.log('abc');
});

We begin this example by including the utils module so we can use the inherits method. inherits provides a way for the EventEmitter class to add its methods to the Server class we created. This means all new instances of Server can be used as EventEmitters.

We then include the events module. However, we want to access just the specific EventEmitter class inside that module. Note how EventEmitter is capitalized to show it is a class. We didn’t use a createEventEmitter method, because we aren’t planning to use an EventEmitter directly. We simply want to attach its methods to the Server class we are going to make.

Once we have included the modules we need, the next step is to create our basic Server class. This offers just one simple function, which logs a message when it is initialized. In a real implementation, we would decorate the Server class prototype with the functions that the class would use. For the sake of simplicity, we’ve skipped that. The important step is to use sys.inherits to add EventEmitter as a superclass of our Server class.

When we want to use the Server class, we instantiate it with new Server(). This instance of Server will have access to the methods in the superclass (EventEmitter), which means we can add a listener to our instance using the on method.

Right now, however, the event listener we added will never be called, because the abc event isn’t fired. We can fix this by adding the code in Example 4-3 to emit the event.

Example 4-3. Emitting an event

s.emit('abc');

Firing the event listener is as simple as calling the emit method that the Server instance inherited from EventEmitter. It’s important to note that these events are instance-based. There are no global events. When you call the on method, you attach to a specific EventEmitter-based object. Even the various instances of the Server class don’t share events. s from the code in Example 4-3 will not share the same events as another Server instance, such as one created by var z = new Server();.

Callback Syntax

An important part of using events is dealing with callbacks. Chapter 3 looks at best practices in much more depth, but we’ll look here at the mechanics of callbacks in Node. They use a few standard patterns, but first let’s discuss what is possible.

When calling emit, in addition to the event name, you can also pass an arbitrary list of parameters. Example 4-4 includes three such parameters. These will be passed to the function listening to the event. When you receive a request event from the http server, for example, you receive two parameters: req and res. When the request event was emitted, those parameters were passed as the second and third arguments to the emit.

Example 4-4. Passing parameters when emitting an event

s.emit('abc', a, b, c);

It is important to understand how Node calls the event listeners because it will affect your programming style. When emit() is called with arguments, the code in Example 4-5 is used to call each event listener.

Example 4-5. Calling event listeners from emit

if (arguments.length <= 3) {
  // fast case
  handler.call(this, arguments[1], arguments[2]);
} else {
  // slower
  var args = Array.prototype.slice.call(arguments, 1);
  handler.apply(this, args);
}

This code uses both of the JavaScript methods for calling a function from code. If emit() is passed with three or fewer arguments, the method takes a shortcut and uses call. Otherwise, it uses the slower apply to pass all the arguments as an array. The important thing to recognize here, though, is that Node makes both of these calls using the this argument directly. This means that the context in which the event listeners are called is the context of EventEmitter—not their original context. Using Node REPL, you can see what is happening when things get called by EventEmitter (Example 4-6).

Example 4-6. The changes in context caused by EventEmitter

> var EventEmitter = require('events').EventEmitter,
...     util = require('util');
> 
> var Server = function() {};
> util.inherits(Server, EventEmitter);
> Server.prototype.outputThis= function(output) {
...   console.log(this);
...   console.log(output); 
... };
[Function]
> 
> Server.prototype.emitOutput = function(input) { 
...   this.emit('output', input);
... };
[Function]
> 
> Server.prototype.callEmitOutput = function() {
...   this.emitOutput('innerEmitOutput');
... };
[Function]
> 
> var s = new Server();
> s.on('output', s.outputThis);
{ _events: { output: [Function] } }
> s.emitOutput('outerEmitOutput');
{ _events: { output: [Function] } }
outerEmitOutput
> s.callEmitOutput();
{ _events: { output: [Function] } }
innerEmitOutput
> s.emit('output', 'Direct');
{ _events: { output: [Function] } }
Direct
true
>

The sample output first sets up a Server class. It includes functions to emit the output event. The outputThis method is attached to the output event as an event listener. When we emit the output event from various contexts, we stay within the scope of the EventEmitter object, so the value of this that s.outputThis has access to is the one belonging to the EventEmitter. Consequently, the this variable must be passed in as a parameter and assigned to a variable if we wish to make use of it in event callback functions.

Get Node: Up and Running now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.