Node 6 LTS is finally here

SHARE ON

A quick look on some of the new features of Node 6.x

This blog post is part of the Mixmax 2016 Advent Calendar. The previous post on December 2nd was about Upgrading to Node 6 on Elastic Beanstalk.

The long-awaited stable LTS version of Node 6.x was released last October, and with it, many
performance and security improvements, and new ES6 syntax features are now natively supported as well.

While some of the new ES6 features can be considered syntax sugar, they also open the door
for much more concise and understandable code, as well as opening for metaprogramming capabilities.
We will now explore some of these features and how it compares to “old” code.

Default arguments, spread operator and destructuring.

One common use case is to have a function that has multiple arguments where one or more of them are
optional. Such a function would look like something similar to:

function makeSandwich(customer, bread, filling) {
  bread = bread || 'wheat';
  filling = filling || 'ham';

  // Make the sandwich
}

We can make that code more concise and simpler to understand by using default arguments in our function:

function makeSandwich(customer, bread = 'wheat', filling = 'ham') {
  // Make the sandwich.
}

How about if we pass our sandwich ingredients as an array?

function makeSandwich(customer, ingredients) {
  const bread = ingredients[0];
  const filling = ingredients[1];

  // Make the sandwich
}

We can make the syntax more concise with the spread operator now!

function makeSandwich(customer, [bread, filling]) {
  // Make the sandwich
}

If we want to retain the ability to keep default ingredients, then we can combine the two features
like so:

function makeSandwich(customer, [bread, filling] = ['wheat', 'ham']) {
  // Make the sandwich
}

Unfortunately, the above will only work as long as the second parameter is undefined, if we pass a
value for bread but not for filling, then filling won’t be defaulted to ‘ham’ as expected.

What if we want to keep our list of ingredients support open and allow for new ingredients later?
Then we can use destructuring and rest !

function makeSandwich(customer, ...ingredients) {
  const [bread, filling] = ingredients;

  // Make the sandwich
}

Later when we can add toppings and sides to our sandwich, we can add that to our destructuring sentence:

function makeSandwich(customer, ...ingredients) {
  const [bread, filling, toppings, sides] = ingredients;

  // Make the sandwich
}

Maybe an array is not the best representation of our ingredients, we can use an object and still use
destructuring to assign properties to local variables:

function makeSandwich(customer, ingredients) {
  const { bread, filling, toppings, sides } = ingredients;

  // Make the sandwich
}

An unoptimized quicksort-like algorithm that uses the spread operator to perform array
concatenations looks like this:

function quicksort(list) {
  const size = _.size(list);

  if (size === 0) return [];
  if (size === 1) return list;

  const [pivot, ...rest] = list;
  const [left, right] = _.partition(rest, item => item < pivot);

  return [...quicksort(left), pivot, ...quicksort(right)];
}

Although not a real quicksort because it does not sorts in-place, it demonstrates how concise
JavaScript code can now be thanks to destructuring and the spread operator in particular.

Map and Sets

The Map and Set objects are actually supported since Node 4.x,
however some details were fleshed out until the Node 6.x releases.

Map

the Map is a key/value data structure (similar to plain objects). A Map, like a plain object,
can store values identified with a key, but unlike an object, we can have some guarantees about
these Map and our keys:

  • When iterating over our map, values are retrieved in insertion order.
  • Unlike an object, a Map does not have extraneous keys from the prototype inheritance
  • They are iterable with for .. of
  • Provides useful functions to interact with the map, such as Map#entries, Map#keys, Map#has
    Map#forEach, among others
// A Map can be instantiated with some initial values, pass an array with arrays whose
// first value is the key and the second is the value to store in the mappings

const map = new Map([
  ['one', 1]
  ['two', 2]
]);

map.set('three', 3);

map.has('two'); // true
map.has('three'); // true
map.has('four'); // false

map.forEach((key, value) => {
  console.log("%s: %s", key, value);
});

for(const [key, value] of map) {
  console.log("%s: %s", key, value);
}

console.log(map.get('one')); // 1
console.log(map.size); // 3

Set

The Set is a particularly useful data structure, it has the property that the Set is a list of
values that can't be repeated.
Similar to Map, the Set object can be interacted with similar methods

const set = new Set();

set.add(1);
set.add(2);
set.add(3);

console.log(set); // Set { 1, 2, 3 }

set.add(3);

console.log(set); // Set { 1, 2, 3 }

console.log(set.size); // 3

for (const item of set) {
  console.log(item);
}

// You can create an array from a set with:
let arr = Array.from(set)

// ... or use the spread operator
arr = [...set];

Metaprogramming with ES6

One of the not-so-talked features of ES6 is the meta programming capability brought to the table
thanks to Symbol and Proxy classes.

Proxy

The Proxy
object adds the ability to intercept attribute access to the proxied object. For example, an
interesting use case is given by the mongojs library, the Mongo database connection is exposed as a
Proxy object where you can access your MongoDB collections as properties of said object. For example:

db.users.find({ /* ...  */ })

Code to enable the above snippet could look something like:

const myCollections = require('path/to/my/collections');
const myConnection = require('path/to/my/db/connection');

const proxy = new Proxy(myConnection, {
  get(conn, prop) {
    if (Reflect.has(myCollections, prop)) {
      return myCollections[prop];
    }

    return conn[prop];
  }
});

module.exports = proxy;

Our proxy object has the target as its first argument, and as a second argument, a handler object
that implements the intercepting functions which will be called when attempting to access an
attribute in the proxy.

In this implementation, we define a get function, which will intercept every single attribute
access to the proxied object. In our implementation here, we use the new
Reflect
class which provides several utilities functions to safely inspect other objects, here we check if
the accessed property is defined in our collections object, if it is, then we return that, otherwise
we delegate the call to the original object.

Symbol

The Symbol
is a new data type, which has some interesting characteristics:

  • It is not instantiable, you can create new symbols with:
const foo = Symbol(‘foo’);
  • Two defined symbols are never equal:
const a = Symbol(‘foo’);
const b = Symbol(‘foo’);

a === b // false

Properties defined with symbols are not enumerable:

const foo = {
  one: 1,
  two: 2
};

const three = Symbol('three');

foo[three] = 3;

Object.keys(foo); // ['one', 'two']

Symbols, then, can be used as special object properties that “hide” some data inside the objects.
Note that, a list of Symbols in an object can still be accessed with
Object.getOwnPropertySymbols() so these properties are not completely hidden, but merely separated
and handled differently than regular properties.

You want to use a Symbol when you want to store object metadata that you don’t want to expose if you
intend your object to be iterated with for … of loops, or properties that you want to hide when
serializing with JSON.stringify.

Another very interesting use case is for implementing interfaces in classes. Javascript exposes
well-defined Symbols, one of the most approachable ones is the Symbol.iterator symbol, if your
class implements a generator function defined as the Symbol.iterator property, then your class
can be looped with for..of and expanded with the spread operator “…”

Let’s make a very silly example, we’ll implement a random iterator where, given an initial list of
values, it iterates them at “random”.

function random(a, b) {
  return Math.floor(Math.random() * (b - a + 1)) + a;
}

class RandomIterator {
  constructor(values) {
    this.values = values;
  }

  *[Symbol.iterator]() {
    const values = this.values.slice(0);

    while (values.length > 0) {
      const next = random(0, values.length - 1);
      const [value] = values.splice(next, 1);

      yield value;
    }
  }
}

const iterator = new RandomIterator([1,2,3,4,5]);

// This will print the values on our RandomIterator in random order.
for(const i of iterator) {
  console.log(i);
}

// You can also use the spread operator!

[...iterator] // Will print the list in the iterator in random order
[...iterator] // Calling multiple times will return the items in different order.

Symbols can open up several ways on how you can interact with your own data structures using plain
javascript code, also, using Proxies and Symbols you can create a whole new level of meta
programming that was not available until now!

Conclusion

The new syntax introduced with ES6 allows to express ideas and abstractions in a more concise way.
Symbols for example open up the idea of mixins implemented in a different way. Default args make it
clearer the signature of a function without checking the implementation. Spread and Destructuring
makes it easier to work with arrays and objects. The Map and Set classes give a better idea of an
intention of some variable (A map is a key/value pair, a set is a list of items without duplicates)
and so on. Many of these ideas could be implemented with ES5 code, but the new syntax makes it so
that these ideas and intentions are explicit and clear by just looking at the code.

Do you want to take advantage of the new syntax offered under Node 6.x? Come join us.

SHARE ON

Written By

Chuy Martinez

Chuy Martinez

From Your Friends At