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
メソッドを呼ぶたびに done
と value
というプロパティを持ったオブジェクトが返ってきます。
今回 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
を拡張すると返ってくるイテレータにメソッドなどを生やせる