Koa is a small and simple web framework, brought to you by the team behind Express, which aims to create a modern way of developing for the web.
In this series you will understand Koa’s mechanics, learn how to use it effectively in the right way to be able to write web applications with it. This first part covers some basics (generators, thunks).
Why Koa?
It has key features that allows you to write web applications easily and fast (without callbacks). It uses new language elements from ES6 to make control flow management easier in Node among others.
Koa itself is really small. This is because unlike nowadays popular web frameworks (e.g. Express), Koa follows the approach of being extremely modular, meaning every module does one thing well and nothing more. With that in mind, let’s get started!
Hello Koa
var koa = require('koa');
var app = koa();
app.use(function *() {
this.body = 'Hello World';
});
app.listen(3000);
Before we get started, to run the examples and your own ES6 code with node, you need to use 0.11.9 or higher version with the --harmony
flag.
As you can see from the example above, there is nothing really interesting going on in it, except that strange little *
after the function keyword. Well, it makes that function a generator function.
Generators
Wouldn’t it be nice, that when you execute your function, you could pause it at any point, calculate something else, do other things, then return to it, even with some value and continue?
This could be just another type of iterator (like loops). Well, that’s exactly what a generator does and the best thing, it is implemented in ES6, so we are free to use it.
Let’s make some generators! First, you have to create your generator function, which looks exactly like a regular function, with the exception, that you put an *
symbol after the function
keyword.
function *foo () { }
Now we have a generator function. When we call this function it returns an iterator object. So unlike regular function calls, when we call a generator, the code in it doesn’t start running, because as discussed earlier, we will iterate through it manually.
function *foo (arg) { } // generator function
var bar = foo(123); // iterator object
With this returned object, bar
, we can iterate through the function. To start and then iterate to the next step of the generator simply call the next()
method of bar
. When next()
is called the function starts or continues to run from where it is left off and runs until it hits a pause.
But besides continuing, it also returns an object, which gives information about the state of the generator. A property is the value
property, which is the current iteration value, where we paused the generator. The other is a boolean done
, which indicates when the generator finished running.
function *foo (arg) { return arg }
var bar = foo(123);
bar.next(); // { value: 123, done: true }
As we can see, there isn’t any pause in the example above, so it immediately returns an object where done
is true
. If you specify a return
value in the generator, it will be returned in the last iterator object (when done
is true
). Now we only need to be able to pause a generator. As said it’s like iterating through a function and at every iteration it yields a value (where we paused). So we pause with the yield
keyword.
yield
yield [[expression]]
Calling next()
starts the generator and it runs until it hits a yield
. Then it returns the object with value
and done
, where value
has the expression value. This expression can be anything.
function* foo () {
var index = 0;
while (index < 2) {
yield index++
}
}
var bar = foo();
console.log(bar.next()); // { value: 0, done: false }
console.log(bar.next()); // { value: 1, done: false }
console.log(bar.next()); // { value: undefined, done: true }
When we call next()
again, the yielded value will be returned in the generator and it continues. It’s also possible to receive a value from the iterator object in a generator (next(val)
), then this will be returned in the generator when it continues.
function* foo () {
var val = yield 'A';
console.log(val); // 'B'
}
var bar = foo();
console.log(bar.next()); // { value: 'A', done: false }
console.log(bar.next('B')); // { value: undefined, done: true }
Error handling
If you find something wrong in the iterator object‘s value, you can use its throw()
method and catch the error in the generator. This makes a really nice error handling in a generator.
function *foo () {
try {
x = yield 'asd B'; // Error will be thrown
} catch (err) {
throw err;
}
}
var bar = foo();
if (bar.next().value == 'B') {
bar.throw(new Error("it's B!"));
}
for…of
There is a loop type in ES6, that can be used for iterating on a generator, the for...of
loop. The iteration will continue until done
is false
. Keep in mind, that if you use this loop, you cannot pass a value in a next()
call and the loop will throw away the returned value.
function *foo () {
yield 1;
yield 2;
yield 3;
}
for (v of foo()) {
console.log(v);
}
yield *
As said, you can yield pretty much anything, even a generator, but then you have to use yield *
. This is called delegation. You’re delegating to another generator, so you can iterate through multiple nested generators, with one iterator object.
function *bar () {
yield 'b';
}
function *foo () {
yield 'a';
yield *bar();
yield 'c';
}
for (v of foo()) {
console.log(v);
}
Thunks
Thunks are another concept that we have to wrap our head around to fully understand Koa. Primarily they are used to assist a call to another function. You can sort of associate it with lazy evaluation. What’s important for us though that they can be used to move node’s callbacks from the argument list, outside in a function call.
var read = function (file) {
return function (cb) {
require('fs').readFile(file, cb);
}
}
read('package.json')(function (err, str) { })
There is a small module for this called thunkify, which transforms a regular node function to a thunk. You can question the use of that, but it turns out it can be pretty good to ditch callbacks in generators.
First we have to transform the node function we want to use in a generator to a thunk. Then use this thunk in our generator as if it returned the value, that otherwise we would access in the callback. When calling the starting next()
, its value will be a function, whose parameter is the callback of the thunkified function. In the callback we can check for errors (and throw
if needed), or call next()
with the received data.
var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);
function *bar () {
try {
var x = yield read('input.txt');
} catch (err) {
throw err;
}
console.log(x);
}
var gen = bar();
gen.next().value(function (err, data) {
if (err) gen.throw(err);
gen.next(data.toString());
})
Take your time to understand every part of this example, because it’s really important for koa to get this. If you focus on the generator part of the example, it’s really cool. It has the simplicity of synchronous code, with good error handling, but still, it happens asynchronously.
To be continued…
These last examples may look cumbersome, but in the next part we will discover tools that takes these out of our code to just be left with the good parts. Also we finally will get to know Koa and its smooth mechanics, which makes web development such an ease.
Update: the second part is out: Getting Started with Koa – part 2
This article is a guest post from Gellért Hegyi.