1. はじめに数日前、プロジェクトでトラバーサルに使用したときに落とし穴に遭遇し、解決するのに 1 日かかりました。ここで覚えておいてください。 2. 問題まず、非常に簡単なトピックを紹介します。配列が与えられたら、1 秒ごとにそれを出力します。ここでは、プロジェクトで始めたコードを貼り付けます。(もちろん、これはビジネスとはまったく関係ありません) const _ = require('lodash'); 定数エコー = 非同期 (i) => { タイムアウトを設定する(() => { コンソールにログ出力します。 }, 5000); } arrs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]とします。 定数タスク = 非同期() => { _.forEach(arrs, 非同期(i) => { echo(i) を待機します。 }) } 定数実行 = 非同期() => { console.log('run-start====>date:', 新しい Date().toLocaleDateString()) タスク() を待機します。 console.log('run-end====>date:', 新しい Date().toLocaleDateString()) } (非同期() => { console.log('開始...') 実行を待機します(); console.log('終了...') })() // 始める... // 実行開始====>日付: 2018-8-25 // 実行終了====>日付: 2018-8-25 // 終わり... // 私 ===> 1 // i ===> 2 // i ===> 3 // i ===> 4 // i ===> 5 // 私 ===> 6 // i ===> 7 // i ===> 8 // i ===> 9 上記のコードと出力が与えられました。ここでのawaitが効果がないのは不思議です。最初は業務を追加したため、業務コードに問題があり、その後コードを抽出しましたが、それでも機能しませんでした。その時、私は本当にawaitを疑っていました。 最後に、質問に対する答えが示されます。lodashのforEachと[].forEachはawaitをサポートしていません。トラバース中にawaitを実行する必要がある場合は、for-ofを使用できます。 正しいコードは次のとおりです。 const _ = require('lodash'); 定数エコー = 非同期 (i) => { 新しい Promise を返します ((resolve,reject)=>{ タイムアウトを設定する(() => { console.log('i===>', i,new Date().toLocaleTimeString()); 解決する(i) ; }, 2000); }) } arrs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]とします。 定数タスク = 非同期() => { // _.forEach(arrs, async (i) => { // echo(ji) を待機します。 // }) // arrs.forEach(async (i)=> { // echo(i) を待ちます。 // }); (定数 i の arrs) の場合 { echo(i) を待ちます。 } } 定数実行 = 非同期() => { console.log('run-start====>date:', 新しい Date().toLocaleDateString()) タスク() を待機します。 console.log('run-end====>date:', 新しい Date().toLocaleDateString()) } (非同期() => { console.log('開始...') 実行を待機します(); console.log('終了...') })() // 出力開始... 実行開始====>日付: 2018-8-26 1 20:51:29 2 20:51:31 3 20:51:33 4 20:51:35 5 20:51:37 6 20:51:39 です 7 20:51:42 です 8 20:51:44 です 9 20:51:46 です 10 20:51:48 実行終了====>日付: 2018-8-26 終わり... 結論問題を解決するときに、消去法を使用できる場合があります。たとえば、この例では、await メカニズムは問題ないはずです。問題がある場合、それをテストする順番は絶対にありません。したがって、残っている問題は、for トラバーサルの原因にしかなりません。 最初は lodash で実装していたので、lodash の forEach が await 処理をしていない(あるいは冗長にしている)のではないかと疑問に思ったかもしれません。このときは、別の方法を試すこともできます。一般的には、経験の問題です。 補足: forEach で async/await を使用する際に発生する問題 1. 問題の説明数日前、プロジェクトで JavaScript の非同期問題が発生しました。 一連のデータがあり、それぞれを非同期で処理する必要があり、処理が同期されることが期待されます。 コードの説明は次のとおりです。 // データを生成する const getNumbers = () => { Promise.resolve([1, 2, 3]) を返す } // 非同期処理 const doMulti = num => { 新しい Promise を返します ((resolve, reject) => { タイムアウトを設定する(() => { if (数値) { 解決(数値 * 数値) } それ以外 { 拒否(新しいエラー('数値が指定されていません')) } }, 2000) }) } // メイン関数 const main = async () => { コンソールにログ出力します。 定数数値 = [1, 2, 3]; nums.forEach(非同期(x) => { const res = doMulti(x) を待機します。 コンソールログ(res); }); コンソールログ('終了'); }; // main() を実行します。 この例では、forEach は各数値を反復処理し、doMulti 操作を実行します。コード実行の結果は次のようになります。まず、start と end がすぐに印刷されます。 2秒後に1、4、9が同時に出力されます。 この結果は予想とは少し異なります。2秒ごとに非同期処理を実行し、1、4、9を順番に出力したいと考えています。したがって、現在のコードは並列に実行される必要がありますが、シリアルに実行されることが期待されます。 forEach ループを for ループに置き換えてみましょう。 定数main = 非同期() => { コンソールにログ出力します。 const nums = getNumbers() を待機します。 for (定数xの数値) { const res = doMulti(x) を待機します。 コンソールログ(res); } コンソールログ('終了'); }; 実行結果は予想どおりで、出力は start、1、4、9、end です。 2. 問題分析考え方は同じですが、使用されるトラバーサル方法が異なります。なぜこのようなことが起こるのでしょうか? MDN で forEach のポリフィルを検索しました。MDN-Array.prototype.forEach() を参照してください。 // ECMA-262、第 5 版、15.4.4.18 の作成手順 // 参考: http://es5.github.io/#x15.4.4.18 if (!Array.prototype.forEach) { Array.prototype.forEach = function(コールバック、thisArg) { var T、k; もしこれがnullの場合 throw new TypeError(' これは null または定義されていません'); } // 1. OをtoObject()を呼び出して、 // |this| 値を引数として渡します。 var O = Object(これ); // 2. lenValueをGet()内部呼び出しの結果とする // 引数「length」を持つ O のメソッド。 // 3. lenをtoUint32(lenValue)とします。 var len = O.length >>> 0; // 4. isCallable(callback) が false の場合、TypeError 例外をスローします。 // 参照: http://es5.github.com/#x9.11 if (typeof コールバック !== "function") { throw new TypeError(callback + ' は関数ではありません'); } // 5. thisArgが指定された場合はTをthisArgとする。そうでない場合は // T は未定義です。 引数の長さが1より大きい場合 T = この引数; } // 6. kを0とする 0 = 0; // 7. k < len の間繰り返します (k < 長さ) の間 var kValue; // a. Pk を ToString(k) とします。 // これは in 演算子の LHS オペランドに対して暗黙的に行われます // b. kPresentをHasPropertyの呼び出し結果とする // 引数 Pk を持つ O の内部メソッド。 // このステップはcと組み合わせることができます // c. kPresentがtrueの場合、 (k が O の場合) { // i. kValueをGet内部呼び出しの結果とする // 引数 Pk を持つ O のメソッド。 k値 = O[k]; // ii. T を引数としてコールバックの内部メソッド Call を呼び出す // kValue、k、および O を含む this 値と引数リスト。 コールバック。呼び出し(T、kValue、k、O); } // d. k を 1 増やします。 関数 } // 8. undefined を返す }; } 上記のポリフィルの setp 7 から、次の手順を簡単に理解できます。 Array.prototype.forEach = 関数 (コールバック) { // これは配列を表します for (let index = 0; index < this.length; index++) { // 各エントリごとにコールバックを呼び出します コールバック(this[index], index, this); }; }; これは、この非同期関数を実行する for ループと同等であるため、並列で実行され、すべての出力結果が一度に生成されます (1、4、9)。 定数main = 非同期() => { コンソールにログ出力します。 const nums = getNumbers() を待機します。 // nums.forEach(async (x) => { // const res = await doMulti(x); // コンソールログ(res); // }); for (let index = 0; index < nums.length; index++) { (非同期x => { 定数 res = doMulti(x) を待機します コンソール.log(res) })(数値[インデックス]) } コンソールログ('終了'); }; 3. 解決策今、私たちは問題を明確に分析しました。前のソリューションでは、forEach の代わりに for-of ループを使用しました。実際には、forEach を変更することもできます。 const asyncForEach = async (配列、コールバック) => { for (let index = 0; index < array.length; index++) { コールバックを待機します(配列[インデックス]、インデックス、配列); } } 定数main = 非同期() => { コンソールにログ出力します。 const nums = getNumbers() を待機します。 asyncForEach(nums, async x => {を待つ 定数 res = doMulti(x) を待機します コンソール.log(res) }) コンソールログ('終了'); }; 主要(); IV. Eslint の問題この時点で、Eslint は別のエラー「no-await-in-loop」を報告しました。この点に関しては、Eslint の公式ドキュメント https://eslint.org/docs/rules/no-await-in-loop でも説明されています。 良い文章: 非同期関数 foo(things) { 定数結果 = []; (const もののもの) { // 良い例: すべての非同期操作がすぐに開始されます。 results.push(bar(thing)); } // すべての非同期操作が実行されているので、ここですべてが完了するまで待機します。 baz を返します(Promise.all(results) を待機します)。 } 悪い書き方: 非同期関数 foo(things) { 定数結果 = []; for (const もののもの) { // 悪い例: 各ループの繰り返しは、非同期操作全体が完了するまで遅延されます results.push(await bar(thing)); } baz(結果)を返します。 } 実は、上記の 2 つの書き方には良い、悪いという違いはありません。この 2 つの書き方の結果はまったく異なります。 Eslint が推奨する「良い書き方」では、非同期操作を実行するときに順序がありませんが、「悪い書き方」では順序があります。使用する具体的な書き方は、ビジネス ニーズに基づいて決定する必要があります。 したがって、ドキュメントの「使用しないべき場合」セクションでは、Eslint は、順次実行が必要な場合はこのルールを無効にできることも述べています。
上記は私の個人的な経験です。参考になれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。間違いや不備な点がありましたら、遠慮なくご指摘ください。 以下もご興味があるかもしれません:
|
<<: MySQLサービスが起動しても接続されない問題の解決策
>>: Nginx を使用して DoNetCore を Alibaba Cloud にデプロイする方法
弊社の Web プロジェクトの 1 つでは、新しい都市の増加によりトラフィックと DB 負荷が増加し...
ブラウザ モジュールの主な機能は、http リクエスト ヘッダーの「User-Agent」の値とブラ...
VMware12.0+Ubuntu16.04+MySQL5.7.22 インストールチュートリアルの詳...
今日は、興味深いトピックについてお話ししましょう。データベースとテーブルを分割することを検討する前に...
今日は618日、主要なショッピングモールはすべてプロモーション活動を行っています。今日は、次のように...
Windows で Nginx を使用するには、Nginx サービスの起動、停止、Nginx のリロ...
テキストの長さに応じて、左側のテキストの幅を自動調整できる状況を実現したい。1行が表示できない場合、...
この記事の冒頭で、以前書いた入門記事の間違いを訂正したいと思います。初心者を再び誤解させないように、...
これまで、CSS の背景の属性には、color、image、repeat、attachment、po...
エフェクトのスクリーンショット:実装コード:コードをコピーコードは次のとおりです。 <!DOC...
iOS 1. URLスキームこのソリューションは基本的に、WeChat、QQ 組み込みブラウザ、QQ...
インストール時間を節約するために、公式の mysql docker イメージを使用して mysql ...
コードをコピーコードは次のとおりです。 <!DOCTYPE html PUBLIC "...
dockerをインストールすると、通常はdockerユーザーグループが作成されます。ステップ2: 現...
Docker は過去 2 年間で非常に人気が高まっています。開発者はすべてのアプリケーションとソフト...