LoginSignup
3
3

More than 5 years have passed since last update.

Koa入門 - Part3: ジェネレータなどKoaの特徴

Posted at

前回までにKoaのアプリを開発するためのio.jsのDockerイメージを作成したり、Babel
でES6で書いたJavaScriptのコンパイルを試しました。今回はジェネレータなどKoaで開発する場合に理解が必要な特徴を勉強していきます。

リソース

KoaのREADMEにGetting startedとして以下のサイトが紹介されていました。

やはり最初のジェネレーター関数を理解するところです。ハンガリーのRisingStackというNode.jsの開発会社のブログを写経していきます。とてもわかりやすい英語で大変勉強になりました。強みにしているのがJavaScript/DevOps/IoTというのも親近感があります。

ジェネレータ関数

ジェネレータ関数はfunctionキーワードの後に*のアスタリスクを付けて定義します。

function *foo() {}

この関数をコールするとイテレータオブジェクトが返ります。通常の関数と異なりコールしても実行されません。返されたイテレータに対して処理を実行します。

> function *foo(arg) {}
> var bar = foo(123);

イテレータオブジェクトbarnext()メソッドをコールしてイテレートを開始します。next()をコールすると関数は開始するか、中断していた場合は次のポイントまで実行されます。この処理ではジェネレータの状態オブジェクトを返します。valueプロパティは現在のイテレーションの値で、この場所でジェネレータは中断しています。もう一つはブール値のdoneプロパティです。こちらはジェネレータが終了したかを示します。

> function *foo(arg) { return arg }
> var bar = foo(123);
> bar.next()
{ value: 123, done: true }

この例では処理の中断はしていないのでdonetrueのオブジェクトがすぐに返ります。もしジェネレータの中でreturnがあれば、最後のイテレータオブジェクト(doneがtrue)が返ります。
次にジェネレータを中断してみます。関数をイテレーションする毎にyieldが処理を中断したときの値を返します。つまりyieldキーワードのところで処理は中断しています。

yield

next()をコールするとジェネレータは開始してyieldのところまで進みます。その場所でvaluedoneを含むオブジェクトを返します。valueは式(expression)の値です、数値や文字列など結果として値を返すものならなんでも良いです。

function *foo() {
  var index = 0;
  while (index < 2) {
    yield index++;
  }
}

var bar = foo();
bar.next()
//{ value: 0, done: false }
bar.next()
//{ value: 1, done: false }
bar.next()
//{ value: undefined, done: true } 

next()を再び実行するとyieldの値が返り処理が再開します。イテレータオブジェクトからジェネレータの中で値を受け取ることもできます。next(val))のようにすると、処理が再開したときにジェネレータに値が返ります。

> function *foo() {
...   var val = yield 'A';
...   console.log(val);
... }
> var bar = foo();
> console.log(bar.next());
{ value: 'A', done: false }
> console.log(bar.next('B'))
B
{ value: undefined, done: true }

エラー処理

間違いを見つけた時はイテレータオブジェクトのthrow()メソッドをコールするとジェネレータの中でエラーをcatchしてくれます。こうするとジェネレータの中のエラーハンドリングがとても良く書けます。

function *foo() {
  try {
    x = yield 'asd B';
  } catch (err) {
    throw err;
  }
}

var bar = foo();
if (bar.next().value == 'B') {
  bar.throw(new Error("its' B!"));
}

for...of

ES6のループにはジェネレータをイテレートするきにつかうfoo...ofループがあります。イテレーションはdonefalseになるまで続きます。注意が必要なのはこのループを使う時にはnext()のコールに値を渡すことができません。ループは渡された値を無視します。

function *foo() {
  yield 1;
  yield 2;
  yield 3;
}

for (v of foo()) {
  console.log(v);
}

yield*

上記のようにyieldでは何でも扱えるため、ジェネレータも可能ですがその場合はyield *を使う必要があります。この処理をデリゲーションといいいます。別のジェネレータをデリゲートすることができるので、複数のネストされたジェネレータのイテレーションを、1つのイテレータオブジェクトを通して操作することができます。

function *bar() {
  yield 'b';
}

function *foo () {
  yield 'a';
  yield *bar();
  yield 'c';
}

for (v of foo()) {
  console.log(v);
}
//a
//b
//c

Thunks

Thunksはまた別のコンセプトですが、Koaを深く知るためには理解が必要です。主に別の関数のコールしやすくするために使われます。サンクは遅延評価とも呼ばれます。重要なことはNode.jsのコールバックを引数から関数のコールとして外に出せるということです。

var read = function(file) {
  return function(cb) {
    require('fs').readFile(file, cb);
  }
}

read('package.json')(function (err, str) {});
}

thunkifyという小さなモジュールは、普通のNode.jsの関数をthunkに変換してくれます。どう使うか疑問に思うかもしれませんが、コールバックをジェネレータの中に放り込むときに便利です。

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

十分な時間をとってこの例をパーツ毎に理解することが必要なのは、Koaを使うときにとても重要なことがあるからです。ジェネレータの使い方が特にクールです。同期的なコードのシンプルさがありながら、エラー処理もしっかりしているのに、これは非同期で動いています。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3