JavaScriptにおけるジェネレーター関数

JavaScriptにおけるジェネレーター関数

この記事ではJavaScriptにおけるジェネレーター関数について説明します。

YouTube Video

JavaScriptにおけるジェネレーター関数

JavaScriptのジェネレーター関数は、一般的な関数とは異なり、遅延実行一時停止と再開ができる特殊な関数です。ジェネレーター関数を理解することで、非同期処理や大規模なデータの逐次処理を効率的に行うことが可能です。ここでは、ジェネレーター関数の仕組み、使い方、そして実用的なユースケースについて詳しく解説します。

ジェネレーター関数とは

ジェネレーター関数は、function* (アスタリスク付きの関数定義) で定義される関数であり、通常の関数とは異なり、関数の実行を途中で一時停止し、その状態を維持しながら再開することができます。ジェネレーター関数は "遅延実行" を可能にし、結果を段階的に返すことで、効率的なメモリ管理や逐次処理を実現します。

構文

1function* generatorFunction() {
2    yield 'First value';
3    yield 'Second value';
4    return 'Done';
5}

このように、ジェネレーター関数は複数の yield 式を持つことができ、yield を使って関数の実行を一時停止します。ジェネレーター関数を呼び出すと、関数の本体は即座には実行されず、ジェネレーターオブジェクト が返されます。このオブジェクトを使って next() メソッドを呼び出すことで、関数が一時停止した位置から再開されます。

ジェネレーター関数の基本的な使い方

次に、ジェネレーター関数の基本的な使用例を見てみましょう。

 1function* simpleGenerator() {
 2    yield 1;
 3    yield 2;
 4    yield 3;
 5}
 6
 7const gen = simpleGenerator();
 8
 9console.log(gen.next()); // { value: 1, done: false }
10console.log(gen.next()); // { value: 2, done: false }
11console.log(gen.next()); // { value: 3, done: false }
12console.log(gen.next()); // { value: undefined, done: true }

ここで注目すべき点は、ジェネレーターが yield のたびに値を返し、done プロパティが false である限り、さらに値があることを示しています。最後の next() の呼び出しでは、done: true が返され、ジェネレーターが終了したことを示します。

for...of構文の利用

for...ofを利用するとジェネレーターの各yieldの値を簡潔に取り出せます。

 1function* simpleGenerator() {
 2    yield 1;
 3    yield 2;
 4    yield 3;
 5}
 6
 7const gen = simpleGenerator();
 8
 9for (const value of gen) {
10    console.log(value); // 1, 2, 3
11}
  • for...ofは、ジェネレーターを含めIterableなオブジェクトを自動的に.next()で繰り返し、donetrueになるまで回します。
  • ループ内でvalueには各yieldの値が順に代入されます。
  • for...ofを使うと、手動で.next()を呼ぶ必要がないため、非常にシンプルです。

yield キーワードと値の一時停止

yield は、ジェネレーター関数内で一時停止のポイントを示すキーワードです。yield の右側にある値は、next() が呼ばれた際に返されます。また、yield は双方向の通信を可能にします。つまり、next() メソッドの引数として値を渡すと、ジェネレーター関数にその値が送信されます。

 1function* generatorWithYield() {
 2    const value1 = yield 'First yield';
 3    console.log('Received value:', value1);
 4    const value2 = yield 'Second yield';
 5    console.log('Received value:', value2);
 6}
 7
 8const gen = generatorWithYield();
 9
10console.log(gen.next());        // { value: 'First yield', done: false }
11console.log(gen.next('Apple')); // Received value: Apple
12                                // { value: 'Second yield', done: false }
13console.log(gen.next('Banana'));// Received value: Banana
14                                // { value: undefined, done: true }

この例では、next('Apple') の呼び出しで、ジェネレーター関数に 'Apple' という値が渡され、それが関数内部で使用されます。

ジェネレーターの状態管理

ジェネレーターは、その実行状態を維持できるため、長いループや逐次処理を簡潔に表現できます。以下の例では、無限に数値を生成するジェネレーターを示します。

 1function* infiniteGenerator() {
 2    let i = 0;
 3    while (true) {
 4        yield i++;
 5        if (i > 10) {
 6            break;
 7        }
 8    }
 9}
10
11const gen = infiniteGenerator();
12
13for (const value of gen) {
14    if (value === 3) break;
15    console.log(value); // 0, 1, 2
16}

このジェネレーターは、while(true) ループで無限に数値を生成し、必要なタイミングで値を取得できます。これにより、大規模なデータセットを効率よく処理することができます。

ジェネレーター関数の応用

ジェネレーター関数は、複数の処理を順次実行するのに適しています。例えば、逐次的にAPIリクエストを処理したり、大きなファイルを分割して処理する場合などに役立ちます。

1function* apiRequestGenerator() {
2    yield fetch('https://codesparklab.com/json/example1.json');
3    yield fetch('https://codesparklab.com/json/example2.json');
4    yield fetch('https://codesparklab.com/json/example3.json');
5}

このように、非同期処理にも使えるジェネレーターは、効率的なデータの逐次処理に大いに役立ちます。

非同期ジェネレーター (async generator)

ES2018 で導入された 非同期ジェネレーター は、asyncyield を組み合わせて、非同期の値を順次返すことができます。これにより、await と組み合わせて非同期処理を簡潔に記述することが可能です。

構文

 1async function* asyncGenerator() {
 2    yield await Promise.resolve(1);
 3    yield await Promise.resolve(2);
 4    yield await Promise.resolve(3);
 5}
 6
 7const gen = asyncGenerator();
 8
 9(async () => {
10    for await (const value of gen) {
11        console.log(value); // 1, 2, 3
12    }
13})();

非同期ジェネレーターは、for await...of ループを使用して値を逐次取得できます。このパターンは、特に非同期データストリームを処理する際に役立ちます。

実用例: ジェネレーターで非同期処理を簡素化する

ジェネレーター関数は、非同期処理のフローを単純化するためにも使用されます。例えば、以下のように yieldPromise を組み合わせることで、非同期操作を同期的に見えるように書くことが可能です。

 1function* asyncFlow() {
 2    const data1 = yield fetch('https://codesparklab.com/json/example1.json');
 3    console.log(data1);
 4    const data2 = yield fetch('https://codesparklab.com/json/example2.json');
 5    console.log(data2);
 6}
 7
 8const gen = asyncFlow();
 9
10function handleAsync(generator) {
11    const next = (promise) => {
12        promise.then((result) => {
13        const { value, done } = generator.next(result);
14        if (!done) {
15            next(value);
16        }
17        });
18    };
19
20    next(generator.next().value);
21}
22
23handleAsync(gen);

このコードは、ジェネレーターを使って逐次的にAPIリクエストを行い、結果を処理しています。

まとめ

ジェネレーター関数は、JavaScriptの強力な機能の一つであり、関数の実行を一時停止・再開できるという特性を持っています。これにより、逐次的な処理、非同期処理、大規模データの効率的な操作が可能となります。ジェネレーターの理解は、JavaScriptにおける高度なテクニックを習得するための重要なステップです。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video