koa, co そして thunk について

Node の Web Framework としては Express が有名だが、
最近は、Node の ES6 実装に伴い、 ES6 の新しい機能を利用した koa という Web Framework が注目を集めている。

koa は ES6 の generators をフルに活用したインターフェイスが特徴で、
今までの JS に慣れている人間は度肝を抜かれること請け合いである。

下は、 koa で レスポンス時間を測るサンプルコードである。きもい

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

// logger

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

// response

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

generators をつかってるのはわかるが、そもそもなんでこんなことになってるのか
その理由は、 koa の中で使われている co にある。

co は generators を利用したライブラリで、
非同期処理を同期処理っぽく書くことを可能にする。

下は、 co で HTTP リクエストを順番に実行するサンプルコードである。

var co = require('co');
var thunkify = require('thunkify');
var request = require('request');
var get = thunkify(request.get);

co(function *(){
  var a = yield get('http://google.com');
  var b = yield get('http://yahoo.com');
  var c = yield get('http://cloudup.com');
  console.log(a[0].statusCode);
  console.log(b[0].statusCode);
  console.log(c[0].statusCode);
})()

co は引数で渡された generators をラップし、中で generators を実行する。
yield が呼ばれるたびに、返された関数を実行し、
コールバックが呼ばれたら、また元の generators を呼びだす(yield の値は、次に generators を呼び出したときの引数である)。

すごく頭いい、天才か。

ところで、 co で yield するオブジェクトは promise や thunk である必要がある。
サンプルコードでは、 request.get を thunkify というモジュールを使って、 thunk にしている。

では、 thunk とはなにか。
これは、端的にいうと、次のような形をした関数のことである。

var fn = function(args){
  return function(callback){
    // some process
    callback();
  };
};

Javascript は一般的に callback を最後の引数として書くが、
thunk は callback のみを引数にとる関数を返す。

thunkify は、大体の場合において、callback が最後の引数であることを利用して、
普通の callback つき関数を thunkify する。

まあ、 promise を渡してもいいんだけど、
内部的には thunk に変換してるし、こっちのほうがシンプルで好み。