In the last episode of Getting Started with Koa we mastered generators and got to a point, where we can write code in synchronous fashion that runs asynchronously. This is good, because synchronous code is simple, elegant, and more reliable, while async code can lead to screaming and crying (callback hell).

This episode will cover tools that take the pain out, so we have to write only the fun parts. It will give an introduction to the basic features and mechanics of Koa.

Previously

// First part
var thunkify = require('thunkify');  
var fs = require('fs');  
var read = thunkify(fs.readFile);

// Second part
function *bar () {  
  try {
    var x = yield read('input.txt');
  } catch (err) {
    console.log(err);
  }
  console.log(x);
}

// Third part
var gen = bar();  
gen.next().value(function (err, data) {  
  if (err) {
    gen.throw(err);
  }

  gen.next(data.toString());
});

This is the last example of the previous post, as you can see, we can divide it into three important parts. First we have to create our thunkified functions, that can be used in a generator. Then we have to write our generator functions using the thunkified functions. Last there is the part where we actually call and iterate through the generators, handling errors and such. If you think about it, this last part doesn't have anything to do with the essence of our program, basically it lets us run a generator. Luckily there is a module that does this for us. Meet co.

co

Co is a generator based flow-control module for node. The code below does exactly the same as the previous example, but we got rid of the generator calling code. The only thing we had to do, is to pass the generator to a function co, call it and it magically works. Well, not magically, it just handles all of the generator calling code for you, so we don't have to worry about that.

var co = require('co');  
var thunkify = require('thunkify');  
var fs = require('fs');

var read = thunkify(fs.readFile);

co(function *bar () {  
  try {
    var x = yield read('input.txt');
  } catch (err) {
    console.log(err);
  }
  console.log(x);
})();

As we have already learned, you can put a yield before anything that evaluates to something. So it isn't just thunks that can be yielded. Because co wants to create an easy control flow, it yields especially at certain types. The currently supported yieldables:

  • thunks (functions)
  • array (parallel execution)
  • objects (parallel execution)
  • generators (delegation)
  • generator functions (delegation)
  • promises.

We already discussed how thunks work, so let's move on to the other ones.

Parrallel execution

var read = thunkify(fs.readFile);

co(function *() {  
  // 3 concurrent reads
  var reads = yield [read('input.txt'), read('input.txt'), read('input.txt')];
  console.log(reads);

  // 2 concurrent reads
  reads = yield { a: read('input.txt'), b: read('input.txt') };
  console.log(reads);
})();

If you yield an array or an object, it will evaluate its content parallelly. Of course this makes sense when the members of your collection are thunks, generators. You can nest, it will traverse the array or object to run all of your functions parallelly. Important: the yielded result will not be flattened, it will retain the same structure.

var read = thunkify(fs.readFile);

co(function *() {  
  var a = [read('input.txt'), read('input.txt')];
  var b = [read('input.txt'), read('input.txt')];

  // 4 concurrent reads
  var files = yield [a, b];

  console.log(files);
})();

You can also achieve parallelism by yielding after the call of a thunk.

var read = thunkify(fs.readFile);

co(function *() {  
  var a = read('input.txt');
  var b = read('input.txt');

  // 2 concurrent reads
  console.log([yield a, yield b]);

  // or

  // 2 concurrent reads
  console.log(yield [a, b]);
})();

Delegation

You can also yield generators as well of course. Notice you don't need to use yield *.

var stat = thunkify(fs.stat);

function *size (file) {  
  var s = yield stat(file);

  return s.size;
}

co(function *() {  
  var f = yield size('input.txt');

  console.log(f);
})();

We went through almost every yielding possibility you will come across using co. Here is a last example (taken from co's github page) to sum it up.

var co = require('co');  
var fs = require('fs');

function size (file) {  
  return function (fn) {
    fs.stat(file, function(err, stat) {
      if (err) return fn(err);
      fn(null, stat.size);
    });
  }
}

function *foo () {  
  var a = yield size('un.txt');
  var b = yield size('deux.txt');
  var c = yield size('trois.txt');
  return [a, b, c];
}

function *bar () {  
  var a = yield size('quatre.txt');
  var b = yield size('cinq.txt');
  var c = yield size('six.txt');
  return [a, b, c];
}

co(function *() {  
  var results = yield [foo(), bar()];
  console.log(results);
})()

I think at this point you mastered generators enough that you have a pretty good idea, how an async flow is done with these tools.
Now it's time to move on to the subject of this whole series, Koa itself!

Koa

What you need to know about koa, the module itself, is not too much. You can even look at its source and it's just 4 files, averaging around 300 lines. Koa follows the tradition that every program you write must do one thing and one thing well. So you'll see, every good koa module (and every node module should be) is short, does one thing and builds on top of other modules heavily. You should keep this in mind and hack according to this. It will benefit everybody, you and others reading your code. With that in mind let's move on to the key features of Koa.

Application

var koa = require('koa');  
var app = koa();  

Creating a Koa app is just calling the required module function. This provides you an object, which can contain an array of generators (middlewares), executed in stack-like manner upon a new request.

Cascading

An important term, when dealing with Koa, is middleware. So let's make it clear first.

**Middleware** in Koa are functions that handle requests. A server created with Koa can have a stack of middleware associated with it.

Cascading in Koa means, that the control flows through a series of middlewares. In web development this is very useful, you can make complex behaviour really simple with this. Koa implements this with generators very intuitively and cleanly. It yields downstream, then the control flows back upstream. To add a generator to a flow, call the use function with a generator. Try to guess why the code below produces A, B, C, D, E output at every incoming request!
This is a server, so the listen function does what you think, it will listen on the specified port (its arguments are the same as the pure node listen).

app.use(function *(next) {  
  console.log('A');
  yield next;
  console.log('E');
});

app.use(function *(next) {  
  console.log('B');
  yield next;
  console.log('D');
});

app.use(function *(next) {  
  console.log('C');
});

app.listen(3000);  

When a new request comes in, it starts to flow through the middlewares, in the order you wrote them. So in the example, the request starts the first middleware, it outputs A, then hits a yield next. When a middleware hits a yield next, it will go to the next middleware and continue that where it was left off. So we're moving to the next one which prints B. Then another jump to the last one, C. There is no more middleware, we downstreamed, now we're starting to step back to the previous one (just like a stack), D. Then the first one ends, E, and we are streamed upwards successfully!

At this point, the koa module itself doesn't include any other complexity - so instead of copy/pasting the documentation from the well-written Koa site, just read it there. Here are the links for these parts:

Let's see an example (also taken from the Koa site), that takes use of the HTTP features. The first middleware calculates the response time. See how easily you can achieve reaching the beginning and the end of a response, and how elegantly you can split these functionality-wise.

app.use(function *(next) {  
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

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);  

Wrapping up

Now that you're familiar with the core of Koa, you can say that your old web framework did all the other fancy things and you want those now! But also remember that there were a ton of features you've never used, or that some worked not the way you wanted. That's the good thing about Koa and modern node frameworks. You add the required features in the shape of small modules from npm to your app, and it does exactly what you need and in a way you need it.

This article is a guest post from Gellért Hegyi.