Generator Function 中の return 句について

新年早々、まさかりを投げてる感があって心苦しいけど、気になったので…。

さて、generatorsの説明は他に任せるとして、いきなりコードです。

function* es6_generator() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  return 5;
}

これを複数回実行して時間を計測するコードはこちら。

console.time('es6_generator');
for (i = 0; i < 1000000; i++) {
  iterator = es6_generator();
  obj = {};
  while (!obj.done) {
    obj = iterator.next();
  }
}
console.timeEnd('es6_generator');

このジェネレータオブジェクトをwhileで単純に回すと、以下のようになりますね。

{ done: false, value: 1 }
{ done: false, value: 2 }
{ done: false, value: 3 }
{ done: false, value: 4 }
{ done: true , value: 5 }

で、最後の { done: true , value: 5 }value は値として有効であるか、否か、です。

結論を言うと、本来的には無効です。

本来的にはというのは、イテレーションをするための構文である「for-of 文での動きでは」ということです。

var iter = es6_generator();
for (var v of iter) {
  console.log(v);
}
// 1
// 2
// 3
// 4

for-of でデバッグプリントすると 4 で終了し、5 は出てこないはずです。

for-of の動き

  1. Let oldEnv be the running execution context’s LexicalEnvironment.
  2. Let V = undefined .
  3. Repeat
    1. Let nextResult be the result of IteratorStep(keys).
    2. ReturnIfAbrupt(nextResult).
    3. If nextResult is false, then return NormalCompletion(V).
    4. Let nextValue be the result of IteratorValue(nextResult).
    5. ReturnIfAbrupt(nextValue).
13.6.4.7 Runtime Semantics: ForIn/OfBodyEvaluation

a の部分で、IteratorStep を呼び出していますが、これはイテレーションを一つ進めて、done プロパティが true なら false, false なら true を返します。つまり、IteratorStep が false を返したら、イテレーションは終了するということです(c. If nextResult is false, then return NormalCompletion(V). 部分)。

イテレーションを手動で書くなら

var iter = es6_generator();
var result = iter.next();
while (!result.done) {
  // ... 処理
  result = iter.next();
}

もっとスマートに書くなら

var iter = es6_generator();
var done, value;
while ({done, value} = iter.next(), !done) { // 分割代入は v8 ではまだ未実装なのでFirefoxで
  // ... 処理
}

と書くのが良いと思います。


最初のコードの return 5; は for-of では評価されませんので無駄です。手動でwhile文を書いて回した時のみ使えるデバッグ値のようなものとして扱えるかもしれませんので、完全に無駄とは言えませんが、やっぱり無駄です。コードを読んだ人を混乱させる可能性すらあります。オススメできません。

下記のように、条件によって即終了を意味するために return を使用するのは問題無いと思いますが、値は返さない(undefinedを返す)方が良いと思います。

function * foo () {
  var i = 0;
  while (true) {
    yield i++;
    if (i > 100) {
      return;
    }
  }
}