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.
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.
Because the
event model is tied to the DOM in browsers, Node created the Event
Emitter
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.
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.
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
Event
Emitter
s.
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.
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();
.
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
.
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.
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.