過去 2 ~ 5 年間 JavaScript 開発者であったなら、ジェネレーターとイテレーターについて述べた投稿に間違いなく遭遇しているはずです。 ジェネレーターとイテレーターは本質的に関連していますが、ジェネレーターは他よりも少し威圧的なように見えます。
Iterator はマップ、配列、文字列などの Iterable オブジェクトへの実装で、 next() で反復処理を行うことができるようにします。
イテレータに初めて触れる方には、次のリンクの Guide to Iterators をお勧めします。
オブジェクトがイテレート可能プロトコルに準拠しているかどうかを確認するには、組み込みの Symbol.iterator を使って検証します。 私は本当に長いことを意味します! だから、そこから逃げることはできないのです。 ES7 と ES8 にはいくつかの新しいアップデートがありますが、ES6 が ES5 から持っていたような、いわば JavaScript を次のレベルに持っていったような、変化の大きさはありません。 もしあなたがプロなら、応答にあなたのコメントを追加することによって、私が内容を改善するのを助けてください。
JavaScript の関数は、周知のように「戻る/終了するまで」実行されます。 一方、ジェネレータ関数は「yield/return/end まで」実行されます。 これは、next() メソッドまたは for… ループを使用して反復できる Generator Iterable 全体を保持します。
Generator で next() を呼び出すたびに、次の yield が発生するまでコードの各行を実行し、一時的にその実行を停止します。
Syntactically they are identified with a *, either function* X or function *X, – both mean the same thing.
一旦作成されると、ジェネレーター関数をコールすると、ジェネレーターオブジェクトが返されます。 このジェネレータ・オブジェクトは、それ自身に呼び出される後続の next() メソッドを追跡するために変数に代入される必要があります。 ジェネレーターが変数に割り当てられていない場合、それは常にすべての next() で最初の yield 式までしか yield しないでしょう。
ジェネレーター関数は通常 yield 式を使って構築されます。 ジェネレーター関数内の各 yield は、次の実行サイクルが始まる前の停止点です。
各 next() 呼び出しで、yield 式は以下のパラメータを含むオブジェクトの形でその値を返します。 2478>
(少し頭で理解できないと感じたら、以下の例を見ればより明確になるでしょう)
Note: 上記の例では、ラッパーなしで直接アクセスされたジェネレーター関数は、常に最初のyieldまでしか実行されません。 したがって、定義上、適切に反復処理するために、ジェネレーターを変数に代入する必要があります。
Generator 関数のライフサイクル
先に進む前に、Generator 関数のライフサイクルのブロック図を簡単に見ておきましょう。
yieldが発生するたびに、ジェネレータ関数は発生した yieldの値と doneステータスを含むオブジェクトを返します。 同様に、returnが発生すると、戻り値とtrueとしてのdoneステータスを取得します。 done ステータスが true として返されるときはいつでも、本質的にジェネレーター関数の実行が完了したことを意味し、それ以上の yield は不可能です。
最初の return の後のすべては、他の yield 式を含めて無視されます。 そして、期待される出力を得ました。
yield 式全体を変数に渡した結果はどうなりましたか? Nothing or Undefined …
Why ? 2番目のnext()から、前のyieldは次の関数で渡される引数に置き換えられる。
このことを念頭に置いて、next()メソッドへの引数の渡し方についてもっと理解するために次のセクションにジャンプしてみましょう。 これは、ジェネレーター実装全体の中で最も厄介な部分の 1 つです。
次のコードの部分を考えてみましょう。ここでは、yield が変数に代入されていますが、今回は next() メソッドで値を渡します。
コンソールで次のコードを見てみましょう。 そして、その直後の説明です。
Explanation:
- 最初の next(20) をコールすると、最初の yield までコードのすべての行が出力されます。 前の yield 式がないため、この値 20 は破棄されます。 出力では、i*10 という yield 値が得られ、これはここでは 100 です。
- 2回目のnext(10)呼び出しでは、最初のyield式をすべて10に置き換え、yield (i * 10) = 10と想像し、const jの値を50に設定して、2度目のyieldの値を返しました。 ここでのyieldの値は2 * 50 / 4 = 25.です。
- 3回目のnext(5)で、2回目のyieldをすべて5に置き換え、kの値を5にしています。 そしてさらにreturn文の実行を続け、最終的なyield値として(x + y + z) => (10 + 50 + 5) = 65をdone trueとともに返す。
初めて読む人にとっては少し圧倒されるかもしれないが、完全に理解するには5分間かけて何度も読んでほしい。
関数の引数として Yield を渡す
function generator 内部でどのように使用できるのか、yield を取り巻く使用事例が多数存在します。 yield の興味深い使い方の 1 つについて、以下のコードとその説明を見てみましょう。
Explanation
- 最初の next() は yield 式が値を持たないので不定値を生成しています。
- 2回目のnext()では、渡された値である「I am usless」が出力される。
- 3番目のnext()は、未定義の引数で関数を呼び出す。 前述したように、引数なしで呼び出されたnext()メソッドは、基本的に前のyield式全体が未定義であることを意味します。
関数呼び出しによるyield
値を返す以外に、yieldは関数を呼び出して値を返したり、同じものを表示することもできます。 以下のコードを見て、理解を深めましょう。
上のコードでは、関数の return obj が yield 値として返されています。 そして、const user に undefined を設定して実行を終了します。
Yield with Promises
Yield with promises は、上記の関数呼び出しと同じアプローチで、関数から値を返す代わりに、成功または失敗についてさらに評価できるプロミスを返します。
apiCall は yield 値として promise を返し、2秒後に解決すると私たちが必要とする値を印刷します。
Yield*
これまでyield式の使用例を見てきましたが、次はyield*という別の式を見ていきます。 yield* はジェネレータ関数の中で使用されると、別のジェネレータ関数を委譲します。 簡単に言うと、次の行に進む前に、式内のジェネレーター関数を同期的に完了させます。
理解を深めるために、コードと以下の説明を見てみましょう。 このコードは MDN Web ドキュメントから引用しました。
Explanation
- 最初の next() コールは値 1 を生成します。
- しかし、2 番目の next() 呼び出しは yield* 式であり、本質的に、現在のジェネレーター関数を続ける前に yield* 式で指定した別のジェネレーター関数を完了することを意味します。
- あなたの頭の中で、上記のコードは以下のように置き換えられると仮定できます
function* g2() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}これでジェネレーター実行を終了することになるのでしょう。
Yield* with Return
Yield* with a return は通常の yield* とは少し異なる動作をします。 yield* が return 文とともに使用されると、その値に対して評価されます。つまり、yield* function() 全体が、関連するジェネレーター関数から返される値に等しくなります。
それをよりよく理解するために、コードと以下の説明を見てみましょう。
Explanation
- 最初の next() では yield 1 に直行しその値を返しています。
- 2番目のnext()では2を返す。
- 3番目のnext()では「foo」を返して「the end」に進み、途中で「foo」を const result に代入している。
- 最後のnext()は実行を終了します。
組み込みの反復可能オブジェクトによるyield*
特筆すべきもう一つの興味深いyield*プロパティがあり、戻り値と同様にyield*もArray、String、Mapなどの反復可能オブジェクトに対して反復することができるのです。
では、それがリアルタイムでどのように動作するか見てみましょう。
このコードでは、式として渡されるすべての反復可能オブジェクトに対して yield* が繰り返されます。 コード自体は自明だと思います。
Best Practices
この上に、すべてのイテレーター/ジェネレーターは for…of ループ上で反復できます。 明示的に呼び出される私たちの next() メソッドと同様に、for…of ループは yield キーワードに基づいて次の反復処理に内部的に移動する。 そして、最後の yield まで反復し、next() メソッドのように return 文を処理することはありません。
for…of ループは最後の yield までしか繰り返さないため、最後の戻り値は表示されません。 そのため、for…ofで反復されたときに関数の再利用性に影響を与えるので、ジェネレータ関数内でreturnステートメントを避けることがベストプラクティスとなっています。
まとめ
以上で、ジェネレータ機能の基本使用例と JavaScript ES6 以上のジェネレータ機能についてのご理解を深められたと幸いです。 もし私のコンテンツを気に入っていただけたなら、1、2、3、あるいは 50 回の拍手をお願いします :).
Please follow me on my GitHub account for more JavaScript and Full-Stack projects:
.