Generator とは何か(まずイメージから)
Generator(ジェネレータ)は、
「少しずつ値を返しながら、一時停止・再開できる特別な関数」 です。
普通の関数は、一気に最後まで実行されて「結果はこれ!」で終わりですが、
Generator は
1 回目呼ぶ → ちょっと実行して止まる(値を 1 つ返す)
2 回目呼ぶ → その続きから実行してまた止まる(次の値を返す)
…
最後まで行ったら「もう終わりです」と教える
という動きをします。
この「少しずつ値を吐き出す」「途中で止まれる」という性質のおかげで、
Generator は 「Iterator を簡単に作るための関数」 として使えます。
ここが重要です。
Iterator の章でやった「next() を持つオブジェクト」を、自分でベタ書きする代わりに、
Generator なら「関数の見た目」で自然に書けるようになります。
Generator の基本文法と yield
function* で定義する
Generator 関数は、function* という形で定義します。
function* gen() {
console.log("最初の処理");
yield 1;
console.log("2つ目の処理");
yield 2;
console.log("最後の処理");
}
JavaScript普通の関数との違いは、function*(アスタリスク)と yield というキーワードを使うことです。
呼び出すと「すぐ実行されない」のがポイント
普通の関数:
function f() {
console.log("実行された");
}
f(); // ここで console.log が走る
JavaScriptGenerator 関数:
function* gen() {
console.log("実行された");
}
const it = gen(); // ここではまだ実行されない
JavaScriptgen() を呼び出した瞬間に処理が走るのではなく、
「イテレータ(Generator オブジェクト)」が返ってくるだけです。
実際の処理は、it.next() を呼んだときに初めて動き始めます。
yield で「値を返しつつ一時停止」
function* gen() {
console.log("A");
yield 1;
console.log("B");
yield 2;
console.log("C");
}
const it = gen();
console.log(it.next()); // "A" を表示 → { value: 1, done: false }
console.log(it.next()); // "B" を表示 → { value: 2, done: false }
console.log(it.next()); // "C" を表示 → { value: undefined, done: true }
JavaScriptここでの流れはこうです。
1 回目の next()
先頭から yield 1 のところまで実行されるvalue: 1, done: false を返したところで「一時停止」
2 回目の next()
前回の yield の次の行から再開 → console.log("B") → yield 2 まで実行value: 2, done: false を返して再び一時停止
3 回目の next()
残り(console.log("C"))を実行して、関数が終わるvalue: undefined, done: true を返す
ここが重要です。
Generator は、
「yield まで実行 → 止まる → 次の next() で続きから再開」 を繰り返す関数です。
Generator は「Iterator でもあり Iterable でもある」
Generator はそのまま for...of で回せる
Generator 関数を呼ぶと「Generator オブジェクト」が返ってきます。
これは
next()を持つ(Iterator)[Symbol.iterator]()も持つ(Iterable)
という性質を持っています。
なので、そのまま for...of で回せます。
function* genNumbers() {
yield 1;
yield 2;
yield 3;
}
for (const n of genNumbers()) {
console.log(n);
}
// 1
// 2
// 3
JavaScriptfor...of ループは、
裏側で next() を呼び続け、done: true になったらループを終えます。
手書き Iterator と Generator の比較
Iterator を手書きするとこうでした。
function createCounter() {
let current = 1;
return {
next() {
if (current <= 3) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
},
};
}
const it = createCounter();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
JavaScript同じことを Generator で書くと:
function* counter() {
yield 1;
yield 2;
yield 3;
}
const it = counter();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
JavaScriptどちらも next() で { value, done } を返しますが、
Generator の方が圧倒的に読みやすく・書きやすいです。
ここが重要です。
「自分で Iterator を手書きする代わりに、Generator で書く」
というのが、Generator を学ぶ最大のモチベーションです。
Generator の実用的な例
無限列を表現する
普通の配列で「無限の数列」を表現することはできませんが、
Generator なら「必要になった分だけ」値を生成できます。
function* infiniteCounter(start = 0) {
let n = start;
while (true) {
yield n++;
}
}
const it = infiniteCounter(10);
console.log(it.next().value); // 10
console.log(it.next().value); // 11
console.log(it.next().value); // 12
JavaScriptwhile (true) ですが、実際には next() を呼んだ分だけしか進みません。
これが「遅延評価(必要になったときにだけ計算)」のイメージです。
範囲(range)を Generator で表現する
Python の range のようなものを Generator で書いてみます。
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for (const n of range(1, 5)) {
console.log(n);
}
// 1 2 3 4 5
const arr = [...range(1, 3)];
console.log(arr); // [1, 2, 3]
JavaScriptrange 関数自体は配列を返しているわけではありません。
「1 から end まで値を順番に返す Generator」を返しているだけです。
必要ならスプレッド構文で配列に変換できます。
ツリー構造などの「階層データ」を順にたどる
例えば、こんなツリー状のデータがあります。
const tree = {
value: 1,
children: [
{ value: 2, children: [] },
{
value: 3,
children: [
{ value: 4, children: [] },
],
},
],
};
JavaScriptこれを「深さ優先」で順にたどる Generator を書いてみます。
function* traverse(node) {
yield node.value;
for (const child of node.children) {
yield* traverse(child);
}
}
for (const v of traverse(tree)) {
console.log(v);
}
// 1 2 3 4
JavaScriptここで出てきた yield* は、
「別の Generator からの値を、そのまま外に流す(委譲する)」という意味です。
ここが重要です。
階層構造やグラフの探索など、「途中で止まりながら順にたどりたい処理」は Generator との相性がとても良い です。
yield に値を渡す(双方向通信)
next(value) で前の yield に値を返せる
少しレベルアップした話ですが、next() は引数を受け取ることができます。
その値は「前回の yield の結果」として Generator 関数に渡されます。
function* gen() {
const x = yield 1; // ここで一時停止
console.log("x:", x);
const y = yield 2;
console.log("y:", y);
}
const it = gen();
console.log(it.next()); // { value: 1, done: false }(yield 1 まで実行)
console.log(it.next("AAA")); // x に "AAA" が入る → { value: 2, done: false }
console.log(it.next("BBB")); // y に "BBB" が入る → { value: undefined, done: true }
JavaScript流れを整理すると:
1 回目 next()yield 1 まで実行して停止。戻り値 { value: 1, done: false }。
ここでは x はまだ決まっていない。
2 回目 next("AAA")
前回「止まっていた yield 1」が「”AAA” という値を返した」とみなされるので、const x = yield 1; の行で x には "AAA" が入る。
その後の処理を進めて yield 2 まで来て停止。
ここが重要です。
Generator と next(value) を使うと、
「一時停止 → 外から値を受け取り → 続きを実行」という双方向のやり取り ができるようになります。
初心者のうちはここまで使えなくても大丈夫ですが、
「そういうこともできる」と知っておくと、Generator の奥行きを感じやすくなります。
async/await と Generator の関係(歴史的な位置づけ)
昔は Generator を非同期処理に使っていた
async/await が出てくる前、
Promise と Generator を組み合わせて「同期っぽく非同期を書く」パターンが流行していました。
ざっくりイメージだけ言うと、
yield で Promise を返しておいて、
外側でそれを待ちつつ、next() で再開していく、
というスタイルです。
今は async function と await があるので、
初心者がわざわざ Generator で非同期を書き始める必要はありません。
今の実務感覚として
今の現場では、
- 非同期処理は
async/await - 「分割して値を出したい」「Iterator を楽に作りたい」のに Generator
という役割分担になっていることが多いです。
ここが重要です。
Generator は「非同期のためのもの」ではなく、「Iterator を作るための強力な道具」として捉えておくとブレない です。
どこまで Generator を使えばいいか(初心者向けガイド)
まず押さえておくと良いライン
初心者のうちは、次の 3 つが分かれば十分です。
ひとつ目に、function* と yield で、
「少しずつ値を返す関数」を作れる。
ふたつ目に、Generator 関数を呼ぶと「イテレータ」が返り、next() や for...of で値を順に取り出せる。
みっつ目に、「自前 Iterator を手で書くより、Generator を書いたほうが読みやすい」
つまり、Iterator の“糖衣構文”として Generator を使える。
余裕が出てきたら触ってみるライン
もし興味が湧いてきたら、次のステップに進むと面白いです。
ネスト構造(ツリーなど)を Generator でたどる
無限列(永遠に増え続ける ID など)を Generator で表現してみるyield* で Generator 同士をつなぐ
さらにもう一歩行くと、next(value) で「外から値を注入する」パターンや、
例外の投げ込み(iterator.throw(error))などもありますが、
これは正直中〜上級者向けです。
まとめ
Generator の本質は、
「一時停止・再開ができる関数であり、Iterator を簡単に実装するための仕組み」 です。
押さえておきたいポイントを整理すると:
function* で Generator 関数を定義し、yield で値を返しつつ一時停止する
Generator 関数を呼ぶと「イテレータ(Generator オブジェクト)」が返り、next() で再開していく
Generator オブジェクトは Iterator であり、同時に Iterable なので for...of やスプレッド構文で扱える
自前 Iterator を書くより、Generator で書いたほうが簡潔で読みやすい
無限列・範囲・ツリー探索など、「順に値を生成する処理」との相性が非常に良い
まずは、小さな Generator を自分で書いてみてください。
function* gen() {
yield "A";
yield "B";
yield "C";
}
for (const x of gen()) {
console.log(x);
}
JavaScriptこれを自分の手で打って、「あ、普通の関数なのに、配列みたいに for…of で回せるんだ」と体感できたら、
あなたの中で Generator はもう「難しい謎の機能」ではなく、「必要に応じて呼び出せる道具」に変わり始めています。
