ジェネレータ
function *ジェネレータ関数の作成に使う構文です。ジェネレータ関数を呼び出すと、ジェネレータオブジェクトが返されます。ジェネレータオブジェクトは、イテレータインターフェースに準拠しています(つまりnextreturnおよびthrow関数を実装しています)。
ジェネレータが必要となる背景には2つの重要な動機があります。

遅延評価

ジェネレータ関数を使用して遅延評価されるイテレータを作成することができます。次の関数は、必要なだけの整数の無限リストを返します:
1
function* infiniteSequence() {
2
var i = 0;
3
while(true) {
4
yield i++;
5
}
6
}
7
8
var iterator = infiniteSequence();
9
while (true) {
10
console.log(iterator.next()); // { value: xxxx, done: false } forever and ever
11
}
Copied!
もちろんイテレータが終了した場合は、以下に示すように{ done: true }の結果を得られます。
1
function* idMaker(){
2
let index = 0;
3
while(index < 3)
4
yield index++;
5
}
6
7
let gen = idMaker();
8
9
console.log(gen.next()); // { value: 0, done: false }
10
console.log(gen.next()); // { value: 1, done: false }
11
console.log(gen.next()); // { value: 2, done: false }
12
console.log(gen.next()); // { done: true }
Copied!

ジェネレータ関数の外部制御

これはジェネレータの真にエキサイティングな部分です。本質的には、関数がその実行を一時停止し、残りの関数実行の制御(運命)を呼び出し元に渡すことができます。
ジェネレータ関数は、呼び出した時には実行されません。単にジェネレータオブジェクトを作るだけです。サンプルの実行とともに次の例を考えてみましょう:
1
function* generator(){
2
console.log('Execution started');
3
yield 0;
4
console.log('Execution resumed');
5
yield 1;
6
console.log('Execution resumed');
7
}
8
9
var iterator = generator();
10
console.log('Starting iteration'); // これはジェネレータ関数の本体の前に実行されます
11
console.log(iterator.next()); // { value: 0, done: false }
12
console.log(iterator.next()); // { value: 1, done: false }
13
console.log(iterator.next()); // { value: undefined, done: true }
Copied!
これを実行すると、次の出力が得られます。
1
$ node outside.js
2
Starting iteration
3
Execution started
4
{ value: 0, done: false }
5
Execution resumed
6
{ value: 1, done: false }
7
Execution resumed
8
{ value: undefined, done: true }
Copied!
  • 関数はジェネレータオブジェクトに対してnextが呼び出された時に1回だけ実行されます
  • 関数はyield文が出現するとすぐに一時停止します
  • 関数はnextが呼び出されたときに再開します
つまり、基本的にジェネレータ関数の実行は、ジェネレータオブジェクトによって制御することができます。
ここまで、ジェネレータを使った通信は、ほとんどがジェネレータがイテレータの値を返す、一方向のものでした。JavaScriptのジェネレータの非常に強力な機能の1つは、双方向の通信を可能にすることです!
  • iterator.next(valueToInject)を使って、yield式の返却値を制御することができます
  • iterator.throw(error)を使ってyield式の位置で例外を投げることができます
次の例は iterator.next(valueToInject)を示しています:
1
function* generator() {
2
var bar = yield 'foo';
3
console.log(bar); // bar!
4
}
5
6
const iterator = generator();
7
// 最初に`yield`された値を取得するまで実行する
8
const foo = iterator.next();
9
console.log(foo.value); // foo
10
// `bar`を注入して処理を再開する
11
const nextThing = iterator.next('bar');
Copied!
次の例は iterator.throw(error)を示しています:
1
function* generator() {
2
try {
3
yield 'foo';
4
}
5
catch(err) {
6
console.log(err.message); // bar!
7
}
8
}
9
10
var iterator = generator();
11
// 最初に`yield`された値を取得するまで実行する
12
var foo = iterator.next();
13
console.log(foo.value); // foo
14
// 処理を再開させ、`bar`エラーを発生させる
15
var nextThing = iterator.throw(new Error('bar'));
Copied!
まとめると、このようになります:
  • yieldは、ジェネレータ関数の通信を一時停止し、関数の外部に制御を渡すことを可能にします
  • 外部から、ジェネレータ関数本体に値を送ることができます
  • 外部から、ジェネレータ関数本体に対して例外をthrowすることができます
これが、どのように便利なのでしょうか? 次のセクションasync/awaitでそれを説明します。
最終更新 9mo ago