An early look at three high-impact ES6 features coming soon to Node.js

Google is currently developing V8 4.9, which will ship in the upcoming Chrome 49 release. V8 4.9 is a particularly exciting update, because it includes support for 91% of the ECMAScript 2015 (ES6) standard. When Node.js 6 launches with these V8 improvements, powerful new language features like destructuring assignment and proxies will work out of the box–without requiring special measures like command line flags or transpilers.

Although Node 6.0 isn’t scheduled for release until April, you can experience a little bit of the future today by compiling Node’s vee-eight-4.9 branch from source code. The vee-eight branches are where Node’s maintainers do the heavy lifting to make Node compatible with new versions of V8. The code from these branches is understandably not suitable for use in production environments, but it’s a fun a way to get an early look at coming improvements.

In this blog post, I’m going to walk you through a handful of the new ES6 features that you can use out of the box in the vee-eight-4.9 branch. It’s worth noting that many of these features are also available for testing in Node 5.x by using Babel or command line flags if you want to follow along without compiling.

Destructuring assignment

Destructuring assignment gives you a succinct way to extract multiple values from objects and arrays and then assign them to variables. It is especially useful when you want to reach into nested objects with multiple levels of depth.

The following example shows you how to extract a single property value from a JavaScript object and assign it to a variable with the same name as the property:

var person = {
  name: {first: "Jean-Luc", last: "Picard"},
  ship: {name: "Enterprise", registry: "NCC-1701-E"},
  rank: "Captain"
};

var {rank} = person;
console.log(rank); 

The example above creates a rank variable that contains the value of the person object’s rank property. When you run that code, it will print the word “Captain” to the console. To assign the value to a specific variable name instead of a variable name that matches the name of the property, you can use the following form instead:

var {rank: myRankVar} = person;
console.log(myRankVar); 

The syntax on the left-hand side of the assignment looks a lot like an object literal. If you want to retrieve a value that is multiple levels deep, you can nest the structure. You can also extract multiple variables at the same time by separating the properties with a comma:

var {name: {first}, ship: {name: shipName}} = person;
console.log(first, shipName);

You can use destructuring assignment inside of for loops, extracting specific properties from each object in a sequence. I find that feature particularly useful in RethinkDB applications when I’m iterating over a set of JSON objects returned by a ReQL query:

co(function*() {
  var conn = yield r.connect();
  var response = yield r.table("scoreboard")
                        .orderBy(r.desc("points"))
                        .limit(5).run(conn);

  for (var {name, points} of response)
    console.log(`${name} has ${points} points`);

  conn.close();
});

The example above shows how to extract the name and points properties from the first five records in the scoreboard table when it is ordered by the highest score.

In addition to destructuring assignment, the example uses several ES6 features that were previously introduced in Node: template strings, generators, and of loops. To learn about those features, you can refer to the post I wrote last year about ES6 features in Node 4.

Default parameter values

ES6 finally makes it possible for JavaScript developers to specify default values for function parameters. When the user invokes a function, the default value will stand in for any missing arguments:

function fn(x = 1, y = 2) {
  return x + y;
}

console.log(fn() == 3);

The real power of this feature emerges when you use it with destructuring. You can access properties that are deeply nested in objects, falling back on default values when the properties are undefined. Parameter destructuring is tremendously useful for functions that accept complex configuration objects.

JavaScript programmers often use the logical or operator for null coalescing, like this:

function fn(opts) {
  var opts = opts || {};
  var myopt = opts.myopt || "default value";
}

With destructuring and default parameter values, we can express the same behavior in a way that is much cleaner and easier to write:

function fn({myopt = "default value"} = {}) {
}

You can also use parameter destructuring in anonymous functions, which is often useful when you perform operations like map and filter. You could use map instead of a for loop to perform the scoreboard example that I included earlier in the blog post:

console.log(response.map(
  ({name, points}) => `${name} has ${points} points`).join("\n"));

Keep in mind that you can’t use parameter destructuring inside a ReQL expression. ReQL expressions evaluate into data structures that the JavaScript client driver translates into the RethinkDB wire protocol. The way that parameter destructuring is parsed and processed by the JavaScript runtime precludes incorporating it into ReQL.

Spread operator

ES6 brings JavaScript the spread operator, which is expressed by typing three periods. The spread operator expands an array by pulling the internal values into the parent context. It’s similar to the “splat” operator available in other programming languages. Here’s an example of how you can use it to combine arrays without concat or push.apply:

var values = [4,5,6];
[1,2,3, ...values] == [1,2,3,4,5,6];

Developers often use apply when they want to pass an array to functions like Math.max that take a top-level set of arguments. You can generally use the spread operator in any case where you might have considered using apply in the past:

Math.max.apply(null, [4, 3, 8, 5]) == 8;
Math.max(...[4, 3, 8, 5]) == 8;

I find the spread operator useful in RethinkDB applications when I’m inserting multiple arrays with records into a table. Without the operator, I’d typically have to concatenate the arrays. The operator lets me do it in place:

r.table("example").insert([...group1, ...group2, ...group3]);

The spread operator itself was technically first introduced in Node 5.x, but it gains some new abilities in V8 4.9 and composes well with other new ES6 features introduced in the update. You can use the spread operator with assignment destructuring, giving you the ability to extract array subsets into different variables:

[first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(rest == [3, 4, 5]);

You can also use the spread operator in a function signature when defining parameters. It will take top-level arguments and turn them into an array:

function fn(...rest) {
  return rest.map(x => x * 2);
}

fn(1, 2, 3, 4, 5) == [2, 4, 6, 8, 10];

When I build a RethinkDB application, I often write some setup code that creates the desired tables if they don’t already exist. I can use the spread operator to make a nice convenience function that wraps that operation:

function createTables(db, ...tables) {
  return r.expr(tables)
          .difference(r.db(db).tableList())
          .forEach(r.db(db).tableCreate(r.row));
}

createTables("mydb", "table1", "table2", "table3").run(conn);

The first parameter is the name of the database where I want to store the new tables. The rest of the parameters are the names of the tables that I want to create.

Next steps

The features described in this blog post are some of most easily applicable language additions in V8 4.9, but there are others that are also worth exploring. Most notably, proxy objects, which let you override property access and other behaviors for wrapped objects. Several ES6 features that previously required strict mode are now generally available, including classes and the let and const keywords.

To try the vee-eight-4.9 branch yourself, you can clone it from GitHub and follow the installation instructions. If you’d like to learn more about RethinkDB, check out our 10-minute guide.

Resources: