前回までにKoaのアプリを開発するためのio.jsのDockerイメージを作成したり、Babel
でES6で書いたJavaScriptのコンパイルを試しました。今回はジェネレータなどKoaで開発する場合に重要な特徴を見ていきます。
リソース
KoaのREADMEにGetting startedとして以下のサイトが紹介されていました。
やはり最初のジェネレーター関数を理解するところです。ハンガリーのRisingStackというNode.jsの開発会社のブログを写経していきます。とてもわかりやすい英語で大変勉強になりました。強みにしているのがJavaScript/DevOps/IoTというのも親近感があります。
ジェネレータ関数
ジェネレータ関数はfunctionキーワードの後に*
のアスタリスクを付けて定義します。
function *foo() {} |
この関数をコールするとイテレータオブジェクトが返ります。通常の関数と異なりコールしても実行されません。返されたイテレータに対して処理を実行します。
> function *foo(arg) {} |
イテレータオブジェクトbar
のnext()
メソッドをコールしてイテレートを開始します。next()
をコールすると関数は開始するか、中断していた場合は次のポイントまで実行されます。この処理ではジェネレータの状態オブジェクトを返します。value
プロパティは現在のイテレーションの値で、この場所でジェネレータは中断しています。もう一つはブール値のdone
プロパティです。こちらはジェネレータが終了したかを示します。
> function *foo(arg) { return arg } |
この例では処理の中断はしていないのでdone
がtrue
のオブジェクトがすぐに返ります。もしジェネレータの中でreturn
があれば、最後のイテレータオブジェクト(done
がtrue)が返ります。
次にジェネレータを中断してみます。関数をイテレーションする毎にyield
が処理を中断したときの値を返します。つまりyield
キーワードのところで処理は中断しています。
yield
next()
をコールするとジェネレータは開始してyield
のところまで進みます。その場所でvalue
とdone
を含むオブジェクトを返します。value
は式(expression)の値です、数値や文字列など結果として値を返すものならなんでも良いです。
function *foo() { |
next()
を再び実行するとyield
の結果が返り処理が再開します。イテレータオブジェクトからジェネレータの中で値を受け取ることもできます。next(val))
のようにすると、処理が再開したときにジェネレータに値が返ります。
> function *foo() { |
エラー処理
間違いを見つけた時はイテレータオブジェクトのthrow()
メソッドをコールするとジェネレータの中でエラーをcatchしてくれます。こうするとジェネレータの中のエラーハンドリングがとても良く書けます。
function *foo() { |
for…of
ES6のループにはジェネレータをイテレートするきにつかうfoo...of
ループがあります。イテレーションはdone
がfalse
になるまで続きます。注意が必要なのはこのループを使う時にはnext()
のコールに値を渡すことができません。ループは渡された値を無視します。
function *foo() { |
yield*
上記のようにyieldでは何でも扱えるため、ジェネレータも可能ですがその場合はyield *
を使う必要があります。この処理をデリゲーションといいいます。別のジェネレータをデリゲートすることができるので、複数のネストされたジェネレータのイテレーションを、1つのイテレータオブジェクトを通して操作することができます。
function *bar() { |
Thunks
Thunks
はまた別のコンセプトですが、Koaを深く知るためには理解が必要です。主に別の関数のコールしやすくするために使われます。サンクは遅延評価とも呼ばれます。重要なことはNode.jsのコールバックを引数から関数のコールとして外に出せるということです。
var read = function(file) { |
thunkifyという小さなモジュールは、普通のNode.jsの関数をthunkに変換してくれます。どう使うか疑問に思うかもしれませんが、コールバックをジェネレータの中に放り込むときに便利です。
var thunkify = require('thunkify'); |
十分な時間をとってこの例をパーツ毎に理解することが必要なのは、Koaを使うときにとても重要なことがあるからです。ジェネレータの使い方が特にクールです。同期的なコードのシンプルさがありながら、エラー処理もしっかりしているのに、これは非同期で動いています。