ループの選択が非同期パフォーマンスに与える影響を理解する
私の最近のプロジェクトでは、NoSQL データベース内の多数のレコード (1,000 件以上) を更新する必要がありました。すべてを一度に更新することは現実的ではなく、プロセスも非同期であるため、それぞれ 20 レコードずつの小さなバッチで処理することにしました。これを forEach を使用したループで実装しました。20 個の各セットが次のセットに進む前に更新されます。しかし、このアプローチを実践した後、これらの小さなバッチ内であっても、すべてのレコードが期待どおりに更新されていないことに気付きました。たとえば、25 レコードのバッチを更新しようとしたとき、正常に更新されたのは 10 件だけでした。他の 10 レコードにはログがなく、更新に失敗したレコードの数はランダムに変化しました (12 の場合、5 の場合、または 7 の場合がありました)。
さらにデバッグを行った結果、これらのレコードは更新プロセス中にスキップされる可能性があることがわかりました。さて、なぜこのようなことが起こったのでしょうか?この問題を調査して動作を理解し、その背後にある潜在的な理由を特定しましょう。
問題
いくつかの調査の結果、すべての反復を待機する適切なメカニズムを持たずに forEach ループ内で非同期関数を使用すると、問題が発生する可能性があることがわかりました。この場合、すべての反復が待機されることを保証せずに、forEach ループ内で async/await を使用しました (以下のコードを参照)。
async function patchRecords(records) { // Here, the length of records is 25 let successfulUpdates = 0; // Initialize a counter for successful updates records.forEach(async (item) => { await databaseName.patch(item); successfulUpdates++; // Increment counter on successful update }); // Return the number of records updated successfully return successfulUpdates; } const response = await patchRecords(records); // Make sure to await this call
forEach メソッドはレコードにパッチを適用するために複数の非同期呼び出しを起動しますが、patchRecords 関数はそれらのパッチが終了するのを待たずにすぐに return ステートメントに到達します。その結果、更新がバックグラウンドで処理されている間、関数は更新が完了するのを待たずに続行します。これにより、保留中の約束が履行されないままになる可能性があり、更新がスキップまたは破棄される可能性があります。
解決策
では、どうすればこれを解決できるでしょうか?ソリューションに入る前に、まずデータを反復処理するさまざまな方法を理解しましょう。基本的に、データの反復処理は
順次 または 並列 で行うという 2 つのアプローチがあります。
forEach ループを使用すると、前述した問題が発生する可能性があります。代わりに、最新の for…of ループまたは単純な for ループ を使用することをお勧めします。これにより、await が適切に動作し、すべての更新が確実に処理されるようになります。スキップされません (以下のコードを参照)。
async function patchRecords(records) { // Here, the length of records is 25 let successfulUpdates = 0; // Initialize a counter for successful updates records.forEach(async (item) => { await databaseName.patch(item); successfulUpdates++; // Increment counter on successful update }); // Return the number of records updated successfully return successfulUpdates; } const response = await patchRecords(records); // Make sure to await this call
一方、レコードを並列処理したい場合は、forEach を再度使用しても機能しません。各非同期コールバックは確かに Promise を返しますが、それらの Promise の一部は待機されていないために履行されないままになる場合があります。代わりに、map を使用して Promise の配列を生成し、Promise.all でそれらを待機します (以下のコードを参照)。
async function patchRecords(records) { let successfulUpdates = 0; // Counter for successful updates for (const item of records) { try { // Query to patch each record await databaseName.patch(item); successfulUpdates++; // Increment counter on success } catch { // Handle any errors } } return successfulUpdates; // Return the number of records updated successfully } const response = await patchRecords(records); // Make sure to await this call
これは、適切なループ方法の選択が非同期操作の期待される結果にどのように大きな影響を与えるかを示しています。使用するループのタイプ (forEach、for…of、または標準の for ループ) は、コードが非同期タスクを適切に管理する上で重要な役割を果たします。
まとめ
私は物事をわかりやすく説明しましたか?以下のコメント欄でお知らせください。
私の経験を共有するのは楽しかったです。あなたにもそれが貴重なものだと感じていただければ幸いです!私はまだこの分野を模索中なので、フィードバックや提案をお待ちしています。 ☺️ 改善できる点がある場合、または取り上げてほしいトピックがある場合は、お気軽にご連絡ください。 ? Webサイト。皆さんの助けを借りて学び、成長できることに興奮しています。 ??
以上がforEach vs. for: 非同期対決!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。