ES6 で提案され、すでに一部の Web ブラウザで実装されている機能に Generator があります。今回をそれを試してみます。

必要な知識

  • npm

必要なものをインストール

例によって babel をインストールしておきます。またコンパイルされた JavaScript は babel-runtime が必要なのでそれもインストールしておきます。

$ npm install -g babel
$ npm install babel-runtime

ジェネレータを作る

0 から順に数字が増えるジェネレータを作ってみます。ジェネレータは function* というキーワードで定義できます。

function* countUp() {
    let i = 0;
    for (;;)
        yield i++;
}

この関数を呼び出すとイテレータオブジェクトが返ってきます。それの next メソッドを呼び出すと関数が実行され yield に渡した値が next の返り値に含まれます。以降、 next メソッドを呼び出すたびに停止した位置から再開されるようになります。

これを使う例が以下です。

let iter = countUp();

iter.next(); // { done: false, value: 0 }
iter.next(); // { done: false, value: 1 }
iter.next(); // { done: false, value: 2 }
iter.next(); // { done: false, value: 3 }
iter.next(); // { done: false, value: 4 }

イテレータの next メソッドを呼ぶたびに donevalue というプロパティを持ったオブジェクトが返ってきます。

今回 for (;;) で無限ループをしているのでジェネレータの実行は終わりませんが、終わりがありそこに到達した場合、 done には true が入ります。

value に入っているのが yield に渡した値です。

応用

この countUp()prototype を拡張すると返ってくるイテレータにメソッドを生やすことができます。

countUp.prototype.take = function*(count) {
    let n = 0;
    let iter = this[Symbol.iterator]();
    let e = iter.next();
    while (n++ < count && (e = iter.next(), !e.done)) {
        yield e.value;
    }
};

この take を使うと引数に与えた数でイテレータが終わります。

let iter = countUp().take(3);

iter.next(); // { done: false, value: 0 }
iter.next(); // { done: false, value: 1 }
iter.next(); // { done: false, value: 2 }
iter.next(); // { done: true, value: undefined }
iter.next(); // { done: true, value: undefined }

まとめ

  • function* でジェネレータを定義できる
  • ジェネレータを呼び出すとイテレータが返ってくる
  • ジェネレータ prototype を拡張すると返ってくるイテレータにメソッドなどを生やせる