この記事は、Huawei クラウド コミュニティ「3 月の読書週間 · あなたの知らない JavaScript | ES6 ジェネレーター、一見同期的な非同期プロセス制御表現スタイル」(著者: Ye Yiyi) から共有されたものです。
ビルダー
ブレークフルラン
JavaScript 開発者がコード内でほぼ例外なく依存している前提があります。つまり、関数の実行が開始されると、その関数は最後まで実行され、他のコードは関数を中断したり関数の間に関数を挿入したりすることはできません。
ES6 では、このランツーエンド機能に準拠しない新しい関数タイプが導入されています。この新しいタイプの関数はジェネレーターと呼ばれます。
var x = 1; 関数 foo() { x++; bar(); // <-- この行は x++ ステートメントと console.log(x) ステートメントの間で実行されます。 console.log('x:', x); } 関数 bar() { x++; } foo(); //×:3
bar() が存在しない場合はどうなるのでしょうか?明らかに結果は 3 ではなく 2 になります。最終結果は 3 なので、bar() は x++ と console.log(x) の間で実行されます。
しかし、JavaScript はプリエンプティブではなく、マルチスレッドでも (まだ) ありません。ただし、foo() 自体が何らかの方法でコードのこの時点で一時停止を示すことができれば、そのような割り込みを協調的な方法 (同時実行) で実装することは可能です。
協調的な同時実行性を実装するための ES6 コードは次のとおりです。
var x = 1; 関数* foo() { x++; 譲る; // 一時停止! console.log('x:', x); } 関数 bar() { x++; } // このジェネレータを制御するためのイテレータを構築します var it = foo(); // ここから foo() を開始してください! it.next(); console.log('x:', x); // 2 バー(); console.log('x:', x); // 3 it.next(); //×:3
- it = foo() 操作はジェネレーター *foo() を実行せず、その実行を制御する反復子 (iterator) を構築するだけです。
- *foo() は yield ステートメントで一時停止し、その時点で最初の it.next() 呼び出しが終了します。この時点では *foo() はまだ実行中でアクティブですが、サスペンド状態です。
- 最後の it.next() 呼び出しは、一時停止された場所からジェネレーター *foo() の実行を再開し、x の現在の値 3 を使用する console.log(..) ステートメントを実行します。
ジェネレーターは、1 回以上開始および停止できる特殊なタイプの関数ですが、必ずしも完了する必要はありません。
入出力
ジェネレーター関数は、関数の基本的なプロパティをいくつか備えた特別な関数です。たとえば、パラメーター (つまり、入力) を受け入れ、値 (つまり、出力) を返すことができます。
関数* foo(x, y) { x * y を返します。 } var it = foo(6, 7); var res = it.next(); 解像度; // 42
実際のパラメータ 6 と 7 をそれぞれパラメータ x と y として *foo(..) に渡します。 *foo(..) は呼び出しコードに 42 を返します。
複数の反復子
イテレータを構築するたびに、実際にはジェネレータのインスタンスが暗黙的に構築されます。このイテレータを通じて制御されるのはジェネレータ インスタンスです。
同じジェネレーターの複数のインスタンスを同時に実行でき、相互に対話することもできます。
関数* foo() { var x = 収量 2; z++; var y = 収量 x * z; console.log(x, y, z); } var z = 1; var it1 = foo(); var it2 = foo(); var val1 = it1.next().value; // 2 <-- 結果 2 var val2 = it2.next().value; // 2 <-- 結果 2 val1 = it1.next(val2 * 10).value; // 40 <-- x:20、z:2 val2 = it2.next(val1 * 5).value; // 600 <-- x:200、z:3 it1.next(val2 / 2); // y:300 // 20300 3 it2.next(val1 / 4); // y:10 // 200 10 3
実行プロセスを簡単に要約します。
(1) *foo() の 2 つのインスタンスが同時に開始され、2 つの next() はそれぞれ yield 2 ステートメントから値 2 を取得します。
(2) val2 * 10 (2 * 10) が最初のジェネレーター インスタンス it1 に送信されるため、x は値 20 を取得します。 z が 1 から 2 に増加すると、yield を介して 20 * 2 が出力され、val1 が 40 に設定されます。
(3) val1 * 5 (40 * 5) が 2 番目のジェネレーター インスタンス it2 に送信されるため、x は値 200 を取得します。 z が 2 から 3 に再びインクリメントされ、その後 200 * 3 が yield 経由で出力され、val2 が 600 に設定されます。
(4) val2 / 2 (600 / 2) が最初のジェネレーター インスタンス it1 に送信されるため、y は値 300 を取得し、xyz の値はそれぞれ 20300 3 として出力されます。
(5) val1 / 4 (40 / 4) が 2 番目のジェネレーター インスタンス it2 に送信されるため、y は値 10 を取得し、xyz の値はそれぞれ 200 10 3 として出力されます。
ジェネレーターが価値を生み出す
プロデューサとイテレータ
それぞれが前の値と特定の関係を持つ一連の値を生成するとします。これを実現するには、最後に生成した値を記憶できるステートフル プロデューサが必要です。
イテレータは、プロデューサーから一連の値を段階的に取得するための明確に定義されたインターフェイスです。 JavaScript イテレータのインターフェイスでは、プロデューサから次の値を取得するたびに next() を呼び出します。
標準の反復子インターフェイスは、数値シーケンス ジェネレーター用に実装できます。
var 何か = (関数 () { var nextValid; 戻る { // for..of ループが必要です [Symbol.iterator]: function () { これを返します。 }、 // 標準のイテレータ インターフェイス メソッド 次へ: function () { if (nextVal === 未定義) { 次の値 = 1; } それ以外 { nextVal = 3 * nextVal + 6; } return { 完了: false、値: nextVal }; }、 }; })(); something.next().value; // 1 something.next().value; // 9 something.next().value; // 33 something.next().value; // 105
next() 呼び出しはオブジェクトを返します。このオブジェクトには 2 つのプロパティがあります。done はイテレータの完了ステータスを識別するブール値です。value はイテレーション値を配置します。
反復可能な
iterable は、その値を反復処理できるイテレータを含むオブジェクトです。
ES6 以降、反復可能から反復子を抽出する方法は、特殊な ES6 シンボル値 Symbol.iterator という名前の関数を反復可能がサポートする必要があります。この関数が呼び出されると、イテレータが返されます。通常、各呼び出しは新しいイテレータを返しますが、これは必須ではありません。
var a = [1, 3, 5, 7, 9]; for (var v of a) { コンソール.ログ(v); } // 1 3 5 7 9
上記のコード スニペットの a は反復可能です。 for..of ループは、Symbol.iterator 関数を自動的に呼び出してイテレータを構築します。
for (何かの var v) { .. }
for..of ループは何かが反復可能であることを期待しているため、その Symbol.iterator 関数を探して呼び出します。
ジェネレータ反復子
ジェネレーターは、イテレーター・インターフェースの next() 呼び出しを通じて一度に 1 つの値を抽出するものとみなすことができます。
ジェネレーター自体は反復可能ではありません。ジェネレーターを実行すると、イテレーターが得られます。
関数 *foo(){ .. } var it = foo();
以前の無限数シーケンス プロデューサーは、次のようなジェネレーターを介して実装できます。
function* something() { var nextValid; while (true) { if (nextVal === 未定義) { 次の値 = 1; } それ以外 { nextVal = 3 * nextVal + 6; } nextVal を生成します。 } }
ジェネレーターは各利回りで一時停止するため、関数 *something() の状態 (スコープ) は維持されます。これは、呼び出し間の変数の状態を維持するためにクロージャーが必要ないことを意味します。
非同期反復子ジェネレータ
関数 foo(x, y) { ajax('http://some.url.1/? x=' + x + '&y=' + y, function (err, data) { if (エラー) { // *main() にエラーをスローします it.throw(err); } それ以外 { // *main() を受信したデータで復元します it.next(データ); } }); } 関数* main() { 試す { var text = yield foo(11, 31); コンソール.ログ(テキスト); } キャッチ (エラー) { コンソール.エラー(エラー); } } var it = main(); //ここから始める! it.next();
yield foo(11,31) では、最初に foo(11,31) が呼び出され、値が返されません (つまり、未定義が返されます)。そのため、データを要求する呼び出しが行われますが、その後実際に行われるのは、yield unknown です。
ここでは、Yield はメッセージ パッシングの意味では使用されず、一時停止/ブロックを実装するためのフロー制御にのみ使用されます。実際には、引き続きメッセージの受け渡しが行われますが、ジェネレーターが動作を再開した後は、一方向のメッセージの受け渡しのみになります。
foo(..) を見てください。この Ajax リクエストが成功すると、以下を呼び出します。
it.next(データ);
これにより、応答データを使用してジェネレーターが再開されます。これは、一時停止された yield 式がこの値を直接受け取ったことを意味します。この値は、ジェネレーター コードの実行を継続するときに、ローカル変数 text に割り当てられます。
要約する
この記事の主な内容を要約しましょう。
- ジェネレーターは ES6 の新しい関数タイプであり、通常の関数のように常に最後まで実行されるわけではありません。代わりに、ジェネレータは実行中に一時停止し (その状態を完全に保存し)、後で一時停止した場所から再開できます。
- yield/next(..) のペアは制御メカニズムであるだけでなく、双方向のメッセージ受け渡しメカニズムでもあります。収率 ..式は基本的に一時停止して値を待ち、後続の next(..) 呼び出しは一時停止された yield 式に値 (または暗黙的に未定義) を返します。
- 非同期制御フローに関するジェネレーターの主な利点は、ジェネレーター内のコードが、自然な同期/逐次的な方法でタスクを表現する一連のステップであることです。秘訣は、可能性のある非同期を yield キーワードの背後に隠し、その非同期をジェネレーターのイテレータを制御するコードの部分に移動することです。
- ジェネレーターは、非同期コードに対してシーケンシャルで同期的なブロッキング コード パターンを維持します。これにより、脳がより自然にコードに従うことができ、コールバック ベースの非同期の 2 つの重要な欠陥のうちの 1 つが解決されます。
クリックしてフォローし、できるだけ早くHuawei Cloudの新しいテクノロジーについて学びましょう~
JetBrains 2024 (2024.1) の最初のメジャー バージョン アップデートは オープンソースです。Microsoft も費用を支払う予定です。なぜオープンソースが依然として批判されているのでしょうか? [復旧] Tencent Cloud バックエンドがクラッシュ: コンソールにログイン後、大量のサービス エラーとデータなし ドイツも 「独立して制御可能」にする必要がある 州政府は 30,000 台の PC を Windows から Linux deepin-IDE に移行し、最終的に達成ブートストラッピング! Visual Studio Code 1.88 がリリースされました. 良い人です、Tencent は Switch を本当に「思考する学習マシン」に変えました. RustDesk リモート デスクトップが起動し、Web クライアントを再構築します. SQLite に基づく WeChat のオープン ソース ターミナル データベースである WCDB がメジャー アップグレードされました.