1. ホーム
  2. javascript

[解決済み] javascriptのforループ内の非同期処理 [重複]について

2022-04-23 13:53:40

質問

以下のフォームのイベントループを実行しています。

var i;
var j = 10;
for (i = 0; i < j; i++) {

    asynchronousProcess(callbackFunction() {
        alert(i);
    });
}

私は、0から10までの数字を示す一連のアラートを表示しようとしています。問題は、コールバック関数がトリガーされた時点で、ループがすでに数回繰り返されているため、より高い値である i . これを修正する方法について、何かお勧めがあれば教えてください。

解決方法は?

その for ループは、すべての非同期処理が開始される間、完了するまで直ちに実行されます。 それらが将来的に完了し、コールバックを呼び出すと、ループインデックス変数 i は、すべてのコールバックに対して最後の値になります。

これは for ループは非同期処理の完了を待たずに次の反復処理に進み、非同期コールバックは将来のある時点で呼び出されるからです。 したがって、ループは反復を完了し、その後、非同期操作が終了したときにコールバックが呼び出されます。 そのため、ループのインデックスは、すべてのコールバックに対して最終的な値で、quot;done"されています。

これを回避するためには、各コールバックごとにループインデックスを一意に保存する必要があります。 Javascriptでは、関数のクロージャにそれを取り込む方法があります。 これは、この目的のためにインライン関数クロージャを作成する方法(以下の最初の例)と、インデックスを渡す外部関数を作成し、インデックスを一意に保持させる方法(以下の2番目の例)のいずれかによって行われます。

2016年現在、JavascriptのES6実装を完全にアップ・トゥ・スペックにしている場合、さらに let を定義するために for ループ変数が定義され、その変数は for ループを使用します(下記3番目の実装)。 ただし、これはES6の実装では後発の機能なので、実行環境がそのオプションをサポートしているかどうか確認する必要があることに注意してください。

.forEach()は独自の関数クロージャを作成するので、反復処理に使用します。

someArray.forEach(function(item, i) {
    asynchronousProcess(function(item) {
        console.log(i);
    });
});

IIFEを使用して独自の関数クロージャを作成する

var j = 10;
for (var i = 0; i < j; i++) {
    (function(cntr) {
        // here the value of i was passed into as the argument cntr
        // and will be captured in this function closure so each
        // iteration of the loop can have it's own value
        asynchronousProcess(function() {
            console.log(cntr);
        });
    })(i);
}

外部関数を作成または変更し、その変数を渡す

を修正することができれば asynchronousProcess() 関数に値を渡して asynchronousProcess() 関数で、このようにcntrをコールバックに返します。

var j = 10;
for (var i = 0; i < j; i++) {
    asynchronousProcess(i, function(cntr) {
        console.log(cntr);
    });
}

ES6を使用 let

ES6を完全にサポートするJavascriptの実行環境がある場合は let の中で for のようなループになります。

const j = 10;
for (let i = 0; i < j; i++) {
    asynchronousProcess(function() {
        console.log(i);
    });
}

let で宣言された for このようなループ宣言を行うと、一意な値である i をループの各呼び出しに使用します (これは、あなたが望むことです)。

プロミスとasync/awaitを使ったシリアライジング

非同期関数がプロミスを返し、非同期処理を並列ではなく次々に実行するためにシリアライズしたい場合、そして、あなたが asyncawait であれば、選択肢は広がります。

async function someFunction() {
    const j = 10;
    for (let i = 0; i < j; i++) {
        // wait for the promise to resolve before advancing the for loop
        await asynchronousProcess();
        console.log(i);
    }
}

これは asynchronousProcess() は一度に飛行中であり for のループは、それぞれが終了するまで進んでもくれません。 これは、非同期処理を並列に実行するこれまでの方式とは異なるので、どのような設計にするかはあなた次第です。 注 await はプロミスで動作するため、関数は非同期処理の完了時に解決/拒否されるプロミスを返さなければなりません。 また await を含む関数が宣言されている必要があります。 async .

非同期処理を並列に実行し Promise.all() 順番に結果を収集するために

 function someFunction() {
     let promises = [];
     for (let i = 0; i < 10; i++) {
          promises.push(asynchonousProcessThatReturnsPromise());
     }
     return Promise.all(promises);
 }

 someFunction().then(results => {
     // array of results in order here
     console.log(results);
 }).catch(err => {
     console.log(err);
 });