Common Javascript Idioms

Javascript has a huge ecosystem.

At the time of writing there were 201,625 packages on npmjs.org

At the time of writing there are 201,625 packages on the Node Package Manager (NPM) website

With a catalog of hundreds of thousands of libraries available to Node.js developers, how do you make sure it's easy to get started with any of those libraries?

By speaking the same language!

There are a handful of patterns so common in Javascript that you'll find them all in many libraries and at least one, and often many in almost every library.

Options POJO

Often, you'll want to be able to configure a tool. When starting the Mocha test runner you might want to set the UI and the reporter. You can do that using a Plain Old Javascript Object. In JS an object is most commonly created using the literal syntax {} but can also be created using new Object() and is basically just a collection of "key/value pairs". The key is used to lookup a specified value and can be almost any object! The value can be anything you think to put in it.

// include the mocha library (which was installed with npm)
var Mocha = require('mocha');

// create an instance of the Mocha Test Runner 
// Use an options object 
var mocha = new Mocha({
    // the key is ui and the value is 'bdd'
    ui: 'bdd',
    // the key is reporter and the value is 'list'
    reporter: 'list'
});

AKA

// include the mocha library (which was installed with npm)
var Mocha = require('mocha');

// define the mocha options POJO
var mochaOptions = {
  ui: 'bdd',
  reporter: 'list'
};

// create an instance of the Mocha Test Runner 
// Use an options object 
var mocha = new Mocha(mochaOptions);

Tons of libraries use this pattern including

jQuery

$.ajax({
  url: 'http://example.com',
  type: 'GET',
  async: true
});

Request

var request = require('request');

request({
  method: 'GET',
  url: 'http://example.com'
});

Twilio

var Twilio = require('twilio');
var client = new Twilio(auth, creds);

client.sendMessage({
    to: '+16515556677',
    from: '+14506667788',
    body: 'word to your mother.'
});

By using an object to pass named arguments into a function, an arbitrary number of required and optional fields can be added without changing the signature of the function which reduces the chances that code relying on the modified function will break.

Take for example the process of searching for a red car with four wheels using some code your coworker wrote:

var cars = findCars(4, "red");

this works for a while but things get harder when you want to want to find a compact sedan made by one of the brands you like

var cars = findCars(4, 'red', 'sedan', 'compact', 'tesla');

and things get down right impossible to read when we want to leave out search options and just find cars by tesla.

var cars = findCars(null, null, null, null, 'tesla');

That code i a mess to maintain, and it gets worse when your coworker wants to make it possible to search for just those cars that have rear wheel drive.

var cars = findCars(4, 'red', 'sedan', 'compact', true, 'tesla');

Notice that by adding this feature to the findCars function, your coworker has broken your old code!

Future you will also be wondering what that true means when you come back to it in a couple of months.

findCars could instead be written to take it's arguments using the options object and the rear wheel drive search features wouldn't have introduced a bug and future you will know what everything means.

var cars = findCars({
  doors: 4,
  color: 'red',
  bodyStyle: 'sedan',
  bodySize: 'compact',
  manufacturer: 'tesla',
  rearDriveOnly: true
});
var cars = findCars({
  color: 'red',
  bodyStyle: 'sedan',
  manufacturer: 'tesla'
});

You'll notice that the options object allows you to call the function many different ways. It's a way in javascript to provide multiple signatures for a function by expecting different options depending on which action should happen. Other languages allow overloading the function signature for directly.

Note: You might have noticed that a POJO looks similar to JSON formatted text. [[elaborate]]

First Class Functions

In Javascript, functions are "First Class Citizens". Anything that can be done with any other variable in Javascript can be done with functions. In fact, functions are just callable objects. ((Aside: Everything in Javascript decends from a common ancestor [[prove it!]]))

Two properties of functions in particular should be pointed out. They are important because each enables a common pattern in Javascript.

Callbacks

The first allows for "callbacks", which is a way to allow other code to execute and call the "callback" function when it is done.

var fs = require('fs');
fs.readFile('sample.txt', function (err, data) {
  console.log("I'm done reading the file so I'm running your callback", err, data);
});

In Node.js, callbacks are traditionally called with a first parameter of err. This value will be null if there is not an error and checking for an error is a common first step when writing a node.js style callback. Let's dress up that first example a bit.

var fs = require('fs');
fs.readFile('sample.txt', function (err, data) {
  if (err) {
    console.error("There was an error reading sample.txt", err);
    return;
  }
  console.log("The contents of sample.txt is: ", data);
});

Closures

The second powers "closures". A closure is best explained first by example.

function makeGreeter (greeting) {
  return function greeter (name) {
     console.log(greeting + ", " + name);
  }
}

var greeter = makeGreeter("Hello");
greeter("World");
// outputs:
// Hello World

In the example above, the value of greeting that was passed as an argument to makeGreeter is available to the greeter even though greeting seems locked into the scope of makeGreeter. [[Need more closure love]]

Chaining

Chaining is a mechanism by which a developer can call multiple methods, sequentially, on an object without much boiler plate.

Chaining works because each method on an object will return the object. Let's look at an example

function makeApple() {
    function setColor (color) {
        apple.color = color;
        return apple;
    }

    function setSize (size) {
        apple.size = size;
        return apple;
    }

    function setWeight (weight) {
        apple.weight = weight;
        return apple;
    }
    // hoisting will ensure this is declared in time.
    var apple = {
        setColor: setColor,
        setSize: setSize,
        setWeight: setWeight
    };
    return apple;
}

var apple = makeApple();
// make a large red apple of weight 8 ounces
apple.setColor('red').setSize('large').setWeight('8 oz');
console.log(apple);
// {color: 'red', size: 'large', weight: '8 oz' ...}

jQuery is notorious for chaining.

// select an element with id="p1"
$("#p1")
  // set the element's color to red
  .css("color", "red")
  // slide the element up over a 2 second duration
  .slideUp(2000)
  // slide the element down over a 2 second duration, but only after the slideUp is finished.
  .slideDown(2000);

SuperTest also leverages chaining to make writing tests more pleasant.

var request = require('supertest')
  , express = require('express');

var app = express();

app.get('/user', function(req, res){
  res.send(200, { name: 'tobi' });
});

request(app)
  .get('/user')
  .expect('Content-Type', /json/)
  .expect('Content-Length', '20')
  .expect(200)
  .end(function(err, res){
    if (err) {
        throw err;
    }
  });

In the example above, the request is instructed what url to call and with what http method, the Content-Type header's expected value is defined, the Content-Length header's expected value is defined, the expected HTTP Status Code is declared and the request will bubble any errors it throws.

Go Do a Thing

Knowing the common patterns that other developers use when writing their libraries should prepare you to consume them. There are also other concepts that build on these other common patterns that you're now better prepared to tackle. (Such as Promises)

The best way to solidify these concepts is through repetition. Grab a package from NPM and hack on something for some practice.