2016-06-22 koajs/koa をためした
koa (koajs/koa) を試している。
経緯としては、次の 3 つの衝突地点だからだ。
- 2016-06-19 の今週の目標「beater を利用した App を 1 つつくる」を達成したい
- 2016-06-21 の generator 学習の成果を活かしたい
- bouzuya/beater の generator 対応を進めたい
generator を使う web application framework で最初に思い浮かんだのが koa だった。
koa の API はそこまで多くない。基本は app.use()
つまり middleware の解釈と挙動を把握すれば大丈夫だ。Source Code の量もそこまで多くないのでざっと読む (外部 npm package は多いが core となる箇所は少ない) 。結論から言うと、次の記事の最後に示された実装が分かりやすい。
Koa(1.x)のmiddlewareの仕組みを調べた - Qiita
直感的な yield next;
による midleware の挙動は co
による generator の入れ子の解決に依存している。
例えば、次のように middlware を設定する。
app.use(mw1);
app.use(mw2);
koa-compose
によりこれらは mw1(mw2(noop()))
のように呼び出される。この形から app.use(function* (next) { yield next; });
の next
が何者か分かる。この next
は処理の実行順序で見たときに次の middleware の返した generator (IterableIterator<T>
) だ。
これは期待する処理の実行順序と「逆順」に generator function を呼び出していることを意味する。これは問題にならない。なぜなら generator function は呼び出した際に generator を返すだけで処理を実行するわけではないからだ。
処理の実行順序の最初にある generator function 、つまり最後の generator function は入れ子になった generator を返す。これは IterableIterator<IterableIterator<IterableIterator ...>>
のような構造だ。この構造は middleware を追加すればするほど階層が深くなる。実際にはそれが何層になっているかは問題ではない。
この構造を co.wrap
で上記の入れ子になった generator を Promise
を返す関数に変換する。最後にその関数を、 Koa.Context
を this
として呼び出せば終わりだ。 co
はこの入れ子になった generator を期待する処理の実行順序どおりに実行してくれる。
基本的な部分はこれで十分だろう。
最初 DefinitelyTyped/DefinitelyTyped から取得した .d.ts
が koa (2.x) のものだと気づかず、 document との食い違いに混乱した。2.x は this
の代わりに引数で Koa.Context
を渡すようで良いのだけど、async
を使っているので、まだ対応しないことにした。