JavaScript | ES6+ 文法:その他の ES6+ 機能 – Generator

JavaScript JavaScript
スポンサーリンク

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 が走る
JavaScript

Generator 関数:

function* gen() {
  console.log("実行された");
}

const it = gen(); // ここではまだ実行されない
JavaScript

gen() を呼び出した瞬間に処理が走るのではなく、
「イテレータ(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
JavaScript

for...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
JavaScript

while (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]
JavaScript

range 関数自体は配列を返しているわけではありません。
「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 functionawait があるので、
初心者がわざわざ 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 はもう「難しい謎の機能」ではなく、「必要に応じて呼び出せる道具」に変わり始めています。

タイトルとURLをコピーしました