Thursday, December 19, 2013

Koa.js explained

Koa.js is a Node.js framework from the Express.js authors. The biggest feature here is that it uses ES6 generators to elegantly fix the callback hell problem that arises in sufficiently complex Node.js apps

For those who are not familiar w/ ES6 generators, here's an illustration:

Consider an asynchronous app that does several IO operations. Using the currently standard javscript callback style, an application might look like this:
app.authenticate(function(loggedIn) {
     app.fileExists("bla.txt", function(exists) {
        app.readFile("bla.txt", function(text) {
            app.sendToClient(text, function() {
                app.log("operation successful")
            })
        })
    })
})
As you can see there's a lot of nesting once you start doing several things in sequence. As it turns out, this causes all sort of problems:

  • hard to throw and catch errors effectively
  • lots of boilerplate code
  • even more boilerplate code when doing parallel asynchronous operations
  • difficult to reason about non-trivial code (e.g. what is in scope where, when there are many parallel things happening?)
ES6 generators are basically functions that can pause and later restart from where they paused. The syntax is the same as a regular function, except that it has an asterisk before the function name, and that it allows the `yield` keyword to be used inside of it
function *myGenerator(a) {
    yield a;
}
The` yield` keyword works almost exactly like `return`, except that if you run the function again, the code will continue executing from the next statement after the yield, instead of running the whole function again. (another difference is that yield is an expression) For example:
function *countTo3() {
    console.log(1);
    yield;
    console.log(2);
    yield;
    console.log(3);
}

countTo3(); // logs 1
countTo3(); // logs 2
countTo3(); // logs 3
With this framework, we can then rewrite that nested application kinda like this:
function *myApp() {
  var loggedIn = yield app.authenticate()
  var exists = yield app.fileExists("bla.txt")
  var text = yield app.readFile("bla.txt")
  yield app.sendToClient(text)
  app.log("operation successful")
}
So basically, every time you call `yield` you're explicitly telling the app to "go away and do other stuff" while waiting for expensive IO operations, which makes it very easy to reason about the code, and comes with all the goodness of readability, proper exception stacking, etc.

Very interesting stuff.