Expressive ES6 features that shine in Node.js 4.0
The developers behind Node.js released version 4.0 last week. The new version is a major update that includes significant improvements and many new language features. It is the first release to incorporate improvements from io.js, a community-driven fork that recently reintegrated with upstream Node.js.
The io.js project emerged earlier this year when prominent Node.js contributors decided to go their own way, seeking more open governance and a more predictable release cycle. They recently resolved their differences with Joyent, the corporate steward of the Node.js project. Following the reconciliation, the upstream maintainers adopted the io.js code base.
Node.js 4.0 includes the latest and greatest version of the V8 JavaScript runtime, which opens the door for developers to use a number of new language features from the ECMAScript 6 (ES6) specification. In previous versions of Node, important ES6 features were only available while using experimental command line flags. Some of our favorite new features supported out of the box in Node.js 4.x include arrow functions, generators, for/of loops, and template strings.
At RethinkDB, we’re pretty excited to start using those new language features in our Node.js demo applications. As you will see, the new syntax works seamlessly with our existing RethinkDB client driver for Node.js.
Generators and yield
We highlighted the power of generators earlier this year when we took
io.js for a spin and demonstrated how developers can use
generators to simplify asynchronous programming. When an
asynchronous task is expressed with a generator, you can make it so that
the yield
keyword suspends execution of the current method and resumes
when the desired operation is complete. Much like the C# programming
language’s await
keyword, it flattens out asynchronous code and allows
it to be written in a more conventional, synchronous style.
Third-party coroutine libraries let you use the yield
keyword to achieve
await-like behavior for any expression that returns a JavaScript Promise.
That means that you can use yield
for asynchronous programming with
existing libraries, such as the RethinkDB client driver. Instead of nested
callback spaghetti or complex Promise chains with anonymous functions, you
get cleaner and more maintainable code. Consider the following example:
Node.js 0.12:
r.connect().then(function(conn) {
return r.table("fellowship").coerceTo("array").run(conn)
.finally(function() { conn.close(); })
})
.then(function(data) {
console.log(data);
});
Node.js 4.0 with ES6:
bluebird.coroutine(function*() {
var conn = yield r.connect();
var data = yield r.table("fellowship").coerceTo("array").run(conn);
console.log(data);
conn.close();
})();
Using yield
for asynchronous operations also simplifies exception
handling. You can use it with conventional try/catch blocks instead of
chaining a catch method.
Arrow functions
Arrow functions provide a more concise syntax for expressing anonymous functions. They implicitly return their value and inherit this
from the parent scope. Using arrow functions instead of the more verbose anonymous function syntax can make your ReQL queries cleaner and more readable:
Node.js 0.12:
r.connect().then(function(conn) {
return r.table("posts")
.map(function(doc) { return doc("comments").count(); })
.reduce(function(left, right) { return left.add(right); })
.default(0).run(conn).finally(function() { conn.close(); });
})
.then(function(data) {
console.log(data);
});
Node.js 4.0 with ES6:
bluebird.coroutine(function*() {
var conn = yield r.connect();
var data = yield r.table("posts")
.map(doc => doc("comments").count())
.reduce((left, right) => left.add(right))
.default(0).run(conn);
console.log(data);
conn.close();
})();
Looping with for
and of
When you iterate over an array in JavaScript with the conventional for/in
syntax, you get the index of each iteration rather than the value. That
typically increases the complexity of looping and requires you to keep the
array around in a separate variable. Node.js 4 introduces support for the
new for
/of
loop syntax, which lets you iterate over the
actual values of the sequence. That can be useful when you iterate over
the return value of a RethinkDB query:
Node.js 0.12:
r.connect().then(function(conn) {
return r.expr([1,2,3])
.map(function(x) { return x.mul(2); })
.run(conn).finally(function() { conn.close(); });
})
.then(function(data) {
for (var i in data)
console.log(data[i]);
});
Node.js 4.0 with ES6:
bluebird.coroutine(function*() {
var conn = yield r.connect();
for (var i of yield r.expr([1,2,3]).map(x => x.mul(2)).run(conn))
console.log(i);
conn.close();
})();
In the example above, I used yield
inside of the for
expression so
that loop starts when the asynchronous query is complete.
Template strings
Template strings provide straightforward support for string
interpolation. Anywhere inside of a template string, you can include an
arbitrary JavaScript expression inside of a ${}
wrapper. The runtime
will evaluate the expressions and include their output within the string
at the designated position. Template strings are much cleaner than
performing conventional string concatenation with the +
operator.
Template strings can also span multiple lines, which is often convenient
if you want to generate HTML output. In RethinkDB applications, template
strings make it easy to generate strings that incorporate the output of a
query:
Node.js 0.12:
r.connect().then(function(conn) {
return r.table("quake")
.orderBy(r.desc(r.row("properties")("mag")))("properties")
.limit(10).run(conn).finally(function() { conn.close(); });
})
.then(function(data) {
for (var quake in data)
console.log("There was a " + data[quake].mag + "magnitude earthquake" + data[quake].place);
Node.js 4.0 with ES6:
bluebird.coroutine(function*() {
var conn = yield r.connect();
var query = r.table("quake")
.orderBy(r.desc(r.row("properties")("mag")))("properties")
.limit(10);
for (var quake of yield query.run(conn))
console.log(`There was a ${quake.mag} magnitude earthquake ${quake.place}`)
conn.close();
})();
Next steps
If you’d like to build a Web application with Node.js 4 and RethinkDB,
consider using Koa, a next-generation Node.js web framework designed
by the team behind Express. Koa is very similar to Express, but it makes
extensive use of generators to provide a cleaner way of integrating
middleware components. When you use Koa, every route handler acts like a
coroutine: you can use yield
inside for asynchronous operations.
The following example demonstrates how to build a simple API backend with an endpoint that returns data from a query:
Node.js 0.12 with Express:
var app = require("express")();
var r = require("rethinkdb");
app.get("/fellowship/species/:species", function(req, res) {
r.connect().then(function(conn) {
return r.table("fellowship")
.filter({species: req.params.species})
.coerceTo("array").run(conn)
.finally(function() { conn.close(); });
})
.then(function(output) { res.json(output); });
});
app.listen(8090);
Node.js 4.0 with ES6 and Koa:
var app = require("koa")();
var route = require("koa-route");
var r = require("rethinkdb");
app.use(route.get("/fellowship/species/:species", function *(species) {
var conn = yield r.connect();
this.body = yield r.table("fellowship")
.filter({species: species})
.coerceTo("array").run(conn);
conn.close();
}));
app.listen(8090);
By bringing the latest stable V8 features to the masses, the latest Node.js release delivers some compelling syntactic improvements. These features are no longer experimental–you can take advantage of them in your applications today.
Give RethinkDB a try with Node 4. You can follow our thirty-second RethinkDB quickstart guide.
Resources
- The official Node.js 4 release announcement
- The official Koa website
- More details about ES6 features in Node.js