Node 7.6 + Koa 2: asynchronous flow control made right

Carlos Vega
ninjadevs
5 min readFeb 23, 2017

--

TL;DR: Node 7.6 was released yesterday with native support for async/await which was a blocker to declare Koa 2 stable; that means we can start using it (no need to provide the harmony flag) to build awesome nodejs applications.

If you’ve never heard about Koa, it’s a “next generation framework for nodejs” developed by the team behind Express. Its first version (currently stable) relies heavily on ES2015 generators.

While V1 is awesome we can always do better and upcoming (more like, almost here) ECMAScript support for asynchronous functions with the async/await syntax opens up a new world of possibilities.

The problem with async operations

If you’re already familiar with callback hell you can totally skip this section

Async in JavaScript has always been a mess as it usually involved leveraging the language’s ability to treat functions as first-class citizens. This means we are able to pass functions (that carry their context with them) around and execute them (call them back, hence the name “callback”) once the asynchronous operation is done.

Sounds neat, right?
Now go ahead and add a couple of async operations that depend on other async operations to be executed first and you get this:

fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})

EDIT: This piece of code serves only as an example of how bad it can get and doesn’t directly relate with Koa.

“This is madness”, I hear you scream.

No, this is hell. It’s Callback Hell.

Since this is not the main topic of this post I’ll just say that newer versions of JavaScript (ES2015, ES2016 and ESNext) have all contributed to address this issue using Promises, Generators and async/await (follow the links if you’ve never heard of those before reading on).

Enter Koa 2

Frameworks such as Express heavily relied on callbacks to perform asynchronous actions and while it’s possible to write fairly clean and readable code using callbacks not every developer out there will know how or even care about doing so. At first, Koa got rid of the aforementioned callback hell using a combination of Promises and Generators but now that we have the async/await syntax available in Node Current we can make use of it.

Your typical Koa hello-world app looks like this:

const koa = require('koa');
const app = new koa();
app.use(function *(next){
var start = new Date;
yield next;
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
});
app.use(function *(){
this.body = 'Hello World';
});

app.listen(3000);

In contraposition, the Koa 2 version would look something like this:

const koa = require('koa');
const app = new koa();
app.use(async (ctx, next) => {
const start = new Date;
console.log(`Started at: ${start}`);
await next();
const ms = new Date - start;
console.log(`Elapsed: ${ms}ms`);
});
app.use(async (ctx) => {
ctx.body = 'Hello world';
});
app.listen(3000);

Yeah, async/await and Generators are deeply related and might look very similar but Generators are way more generic. Actually, you can implement your own version of async/await with Generators and Promises

Koa Middleware

“Koa middleware are simple functions which return a MiddlewareFunction with signature (ctx, next). When the middleware is run by an "upstream" middleware, it must manually invoke next() to run the "downstream" middleware.” — Koa 2.x Guide

Koa middleware can be implemented using a stack flow which allows us to easily manage request/response flows. Our small middleware here logs the elapsed milliseconds since the request was issued:

app.use(async (ctx, next) => {
const start = new Date;
console.log(`Started at: ${start}`);
await next();
const ms = new Date - start;
console.log(`Elapsed: ${ms}ms`);
});

If you’ve never used Koa you’re probably wondering what the heck is that await next() doing there, in the middle of our code. Well, if that’s the case just think about the two ways of event propagation: capture and bubble. The code before the next() call is analogous to event capturing (capture down) and the code after that corresponds to event bubbling (bubble up). To further illustrate, let’s take a look at the following example:

...app.use(async(ctx, next) => {
// Step 1
const start = new Date;
console.log(`Started at: ${start}`);
await next(); // Step 2
// Step 7
const ms = new Date - start;
console.log(`Elapsed: ${ms}ms`);
});
app.use(async(ctx, next) => {
// Step 3
console.log(`Hey, I'm just another middleware`);
await next(); // Step 4
// Step 6
console.log('More stuff after body has been set');
});
app.use(async(ctx) => {
const name = await dbCall();
// Step 5
ctx.body = `Hello ${name}`;
console.log('Body has been set');
});
...

The flow goes down like this:

1- Log start date in middleware 1
2- Go to the next middleware, the execution of the next two lines is paused
3- Do stuff in middleware 2
4- Go to the next middleware, the execution of the next line is paused

Once we’ve hit the last middleware the flow continues as follows:

5- Body is set, “bubble” up, since there are no more next() calls
6- Do stuff in middleware 2 after body is set
7- Do stuff in middleware 1 after body is set and Step 6 is done

As you can see the execution goes all the way down and then it goes back all the way up providing a really nice way to create middleware and compose request/response flows with ease.

Takeaway

Koa is an awesome, lightweight framework to develop nodejs applications that just stays out of your way. If you come from an Express background you’ll find it fundamentally different but familiar enough and refreshingly easy to understand.

Koa vs Express (https://github.com/koajs/koa/blob/master/docs/koa-vs-express.md)

As you can see, Koa is basically a middleware kernel. Thankfully there’s a huge catalog of middleware available that you can use to provide routing, templating, caching, authentication, sessions, security, etc.

If you’d like to run the code presented in this post in your machine just head to our Github.

Disclaimer: I’m not a native English speaker, so feel free to point out grammatical and/or syntactical errors. Every respectful comment is deeply appreciated.

--

--

Carlos Vega
ninjadevs

Software engineer in love with web development. Avid reader and occasional blogger. He will blog about anything that crosses his mind. Costa Rica.