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()
で繰り返し、done
がtrue
になるまで回します。- ループ内で
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 で導入された 非同期ジェネレーター は、async
と yield
を組み合わせて、非同期の値を順次返すことができます。これにより、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
ループを使用して値を逐次取得できます。このパターンは、特に非同期データストリームを処理する際に役立ちます。
実用例: ジェネレーターで非同期処理を簡素化する
ジェネレーター関数は、非同期処理のフローを単純化するためにも使用されます。例えば、以下のように yield
と Promise
を組み合わせることで、非同期操作を同期的に見えるように書くことが可能です。
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チャンネルもご覧ください。