• No results found

For a module to expose an API that’s expressed as the return value of a require call, two

globals, module and exports ,come into play.

By default, each module exports an empty object {}. If you want to add properties to it, you

can simply reference exports:

module_a.js

exports.name = ‘john’;

exports.data = ‘this is some data’;

var privateVariable = 5;

exports.getPrivate = function () { return privateVariable;

};

Now test it out (see Figure 4-3):

index.js

var a = require(‘./module_a’); console.log(a.name);

console.log(a.data);

console.log(a.getPrivate());

Figure 4-3: Showing the values exposed by the API of module_a

In this case, exports happens to be a reference to module.exports, which is an object by

default. If setting individual keys in this object is not enough, you can also override module. exports completely. This is a common use case for modules that export constructors (see

C H A P T E R 4 • Node JavaScript

45

person.js

module.exports = Person;

function Person (name) { this.name = name; };

Person.prototype.talk = function () { console.log(‘my name is’, this.name); };

index.js

var Person = require(‘./person’); var john = new Person(‘john’); john.talk();

Figure 4-4: OOP-style JavaScript with Node.JS modules example

As you can see, in this index you no longer receive an Object as the return value, but a Function, thanks to overriding module.exports.

EVENTS

One of the fundamental APIs in Node.JS is the EventEmitter. In both Node and browser

JavaScript, a lot of the code depends on events you listen on or events you emit:

window.addEventListener(‘load’, function () { alert(‘Window is loaded!’);

});

The DOM APIs in the browser that deal with events are mainly addEventListener, removeEventListener, and dispatchEvent. They are present on a number of different

46

PA R T I • Getting Started: Setup and Concepts

The following example makes an AJAX request (in modern browsers) and listens on the

stateChange to know when data is ready:

var ajax = new XMLHTTPRequest

ajax.addEventListener(‘stateChange’, function () { if (ajax.readyState == 4 && ajax.responseText) { alert(‘we got some data: ‘ + ajax.responseText); }

});

ajax.open(‘GET’, ‘/my-page’); ajax.send(null);

In Node, you also listen to and emit events everywhere. Node therefore exposes the Event Emitter API that defines on, emit, and removeListener methods. It’s exposed as process.EventEmitter:

eventemitter/index.js

var EventEmitter = require(‘events’).EventEmitter , a = new EventEmitter;

a.on(‘event’, function () { console.log(‘event called’); });

a.emit(‘event’);

This API a lot less verbose than the DOM equivalent, and Node uses it internally and lets you easily add it to your own classes:

var EventEmitter = process.EventEmitter , MyClass = function (){};

MyClass.prototype._proto__ = EventEmitter.prototype;

Therefore, all the instances of MyClass have encapsulated events support:

var a = new MyClass;

a.on(‘some event’, function () { // do something

});

Events are central to Node’s non-blocking design. Since Node usually doesn’t “respond right away” with data (because that would imply blocking the thread while waiting on a resource), it usually emits events with data instead.

As an example, consider an HTTP server again. When Node fires the callback with an incoming request, all its data might not be immediately available. This is the case for example for POST requests (that is, the user submitting a form).

C H A P T E R 4 • Node JavaScript

47

http.Server(function (req, res) { var buf = ‘’;

req.on(‘data’, function (data) { buf += data;

});

req.on(‘end’, function () {

console.log(‘All the data is ready!’); });

});

This is a common use-case in Node.JS: you “buffer” the contents of the request (data event), and then you can do something with it when you’re sure all the data has been received (end event).

In order for Node to let you know that a request has hit the server as soon as possible, regardless of whether all its data is present or not, it needs to rely on events. Events in Node are the

mechanism by which you get notified of things that haven’t occurred yet, but are bound to occur. Whether an event will be fired or not depends on the API that implements it. For example, you know that ServerRequest inherits from EventEmitter, and now you also know

that it emits data and end events.

Certain APIs emit error events, which might or might not happen at all. There are events that only fire once (like end), or others that could fire more than once (like data). Some APIs only emit a certain event when certain conditions are met. For example, after a certain event happens some other event might be guaranteed not to be fired again. In the case for an HTTP request, you fully expect no data events to happen after an end event. Otherwise, your app would malfunction.

Similarly, sometimes for the use case of your application you only care about registering a callback for an event only once, regardless if it fires again in the future. Node provides a shortcut method for this:

a.once(‘an event’, function () {

// this function will be called only once, even if the event is triggered again });

To understand what type of events are available and what their contracts (the “rules” the given API defines for triggering them) are, you usually refer to the API documentation of the given module. Throughout the book you’ll learn the core Node module APIs and some of the most important events, but always keeping the API handy will be a very helpful habit.

BUFFERS

Another deficiency in the language that Node makes up for, besides modules, is handling of binary data.

48

PA R T I • Getting Started: Setup and Concepts

Buffer is a global object that represents a fixed memory allocation (that is, the number of

bytes that are put aside for a buffer have to be known in advance), which behaves like an array of octets, effectively letting you represent binary data in JavaScript.

A part of its functionality is the capability to convert data between encodings. For example, you can create a buffer from the base64 representation of an image and then write it down

to a file as a binary PNG that can actually be used:

buffers/index.js

var mybuffer = new Buffer(‘==ii1j2i3h1i23h’, ‘base64’): console.log(mybuffer);

require(‘fs’).writeFile(‘logo.png’, mybufffer);

For those not familiar with base64, it’s essentially a way of writing binary data with only ASCII characters. In other words, it allows you to represent something as complex as an image in simple English characters (therefore taking up a lot more hard drive space).

Most of the Node.JS APIs that do data IO take and export data as buffers. In this example, the

writeFile API from the filesystem module takes a buffer as a parameter to write out the file logo.gif.

Run it and open the file (see Figure 4-5).

$ node index $ open logo.png

As you can see as the result of the console. log call with the Buffer object, it’s a

simple interface to the raw bytes that make up an image.

SUMMARY

You have now looked at the major differences between the JavaScript you write for the browser and the one you write for Node.JS.

You have a basic grasp of the APIs that Node added for patterns that are extremely common in day-to-day JavaScript but absent from the main language specification, such as timers, events, binary data, and modules.

You know the equivalent of window in Node world, and that you can leverage existing developer utilities like console.

Figure 4-5: The GIF file created from the buffer base64 representation in the script showing the Node.JS logo