Using RethinkDB with io.js: exploring ES6 generators and the future of JavaScript
A group of prominent Node.js contributors recently launched a community-driven fork called io.js. One of the most promising advantages of the new fork is that it incorporates a much more recent version of the V8 JavaScript runtime. It happens to support a range of useful ECMAScript 6 (ES6) features right out of the box.
Although io.js is still too new for production deployment, I couldn’t resist taking it for a test drive. I used io.js and the experimental rethinkdbdash driver to get an early glimpse at the future of ES6-enabled RethinkDB application development.
ES6 in Node.js and io.js
ES6, codenamed Harmony, is a new version of the specification on which the JavaScript language is based. It defines new syntax and other improvements that greatly modernize the language. The infusion of new hotness makes the development experience a lot more pleasant.
Node.js has a special command-line flag that allows users to enable experimental support for ES6 features, but the latest stable version of Node doesn’t give you very much due to its reliance on a highly outdated version of V8. The unstable Node 0.11.x pre-release builds provide more and better ES6 support, but still hidden behind the command-line flag.
In io.js, the ES6 features that are stable and maturely-implemented in V8 are flipped on by default. Additional ES6 features that are the subject of ongoing development are still available through command-line flags. The io.js approach is relatively granular, but strongly encourages adoption of features that are considered safe to use.
Among the most exciting ES6 features available in both Node 0.11.x and io.js is
support for generators. A generator function, which is signified by
putting an asterisk in front of the name, outputs an iterator instead of a
conventional return value. Inside of a generator function, the developer uses
the yield
keyword to express the values that the iterator emits.
It’s a relatively straightforward feature, but some novel uses open up a few
very interesting doors. Most notably, 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 will suspend
execution of the current method and resume 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.
Introducing rethinkdbdash
Developed by RethinkDB contributor Michel Tu, rethinkdbdash is an
experimental RethinkDB driver for Node.js that provides a connection pool and
several other advanced features. When used in an environment that supports
generators, rethinkdbdash optionally lets you handle asynchronous query
responses with the yield
keyword as an alternative to callbacks or promises.
The following example uses rethinkdbdash with generators to perform a sequence of asynchronous operations. It will create a database, table, and index, which it will then populate with remote data:
var bluebird = require("bluebird");
var r = require("rethinkdbdash")();
var feedUrl = "earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_month.geojson";
bluebird.coroutine(function *() {
try {
yield r.dbCreate("quake").run();
yield r.db("quake").tableCreate("quakes").run();
yield r.db("quake").table("quakes")
.indexCreate("geometry", {geo: true}).run();
yield r.db("quake").table("quakes")
.insert(r.http(feedUrl)("features")).run();
}
catch (err) {
if (err.message.indexOf("already exists") == -1)
console.log(err.message);
}
})();
Each time that the path of execution hits the yield
keyword, it jumps out and
waits for the operation to finish before continuing. The behavior is similar to
what you would get if you used a promise chain, separating each operation into
a then
method call. The following is the equivalent code, as you would write
it today using promises and the official RethinkDB JavaScript driver:
var conn;
r.connect().then(function(c) {
conn = c;
return r.dbCreate("quake").run(conn);
})
.then(function() {
return r.db("quake").tableCreate("quakes").run(conn);
})
.then(function() {
return r.db("quake").table("quakes").indexCreate(
"geometry", {geo: true}).run(conn);
})
.then(function() {
return r.db("quake").table("quakes")
.insert(r.http(feedUrl)("features")).run(conn);
})
.error(function(err) {
if (err.msg.indexOf("already exists") == -1)
console.log(err);
})
.finally(function() {
if (conn)
conn.close();
});
The built-in connection pooling in rethinkdbdash carves off a few lines by
itself, but even after you factor that in, the version that uses the yield
keyword is obviously a lot more intuitive and concise. The generator approach
also happens to be a lot more conducive to using traditional exception-based
error handling.
Use rethinkdbdash in a web application
To build a working application, I used rethinkdbdash with 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.
The following example defines a route served by the application that returns the value of a simple RethinkDB query. It fetches and orders records, emitting the raw JSON value:
var app = require("koa")();
var route = require("koa-route");
var r = require("rethinkdbdash")();
app.use(route.get("/quakes", function *() {
try {
this.body = yield r.db("quake").table("quakes").orderBy(
r.desc(r.row("properties")("mag"))).run();
}
catch (err) {
this.status = 500;
this.body = {success: false, err: err};
}
}));
When the asynchronous RethinkDB query operation completes, its output becomes
the return value of the yield
expression. The route handler function takes
the returned JSON value and assigns it to a property that represents the
response body for the HTTP GET request. It doesn’t get much more elegant than
that.
A promising future awaits
Generators offer a compelling approach to asynchronous programming. In order to
make the pattern easier to express and use with promises, developers have
already proposed adding an official await
keyword to future versions of
the language.
By bringing the latest stable V8 feature to the masses, the io.js project holds
the potential to bring us the future just a little bit faster. Although the
developers characterize it as “beta” quality in its current form, it’s worth
checking out today if you want to get a tantalizing glimpse of what’s coming
.next()
.
Give RethinkDB a try with io.js or Node. You can follow our thirty-second RethinkDB quickstart guide.
Resources
- The official io.js website and GitHub repo
- Michel Tu’s rethinkdbdash library
- The official Koa website
- A sample RethinkDB application built with rethinkdbdash and Koa