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: