web-dev-qa-db-ja.com

forEach内でsetTimeoutが機能しない

関数を呼び出すforEachがあります。呼び出されるたびに遅延が必要です。 forEach内のsetTimeout内に配置しました。最初の待機後のタイムアウトは考慮されません。代わりに、一度待機してから、すべてを一度に実行します。タイムアウトを5秒に設定し、コンソールを使用して確認しています。 5秒間待機してから、いくつかのfoobarコンソールが一度にすべてログに記録します。

なぜこの動作が発生するのですか?

var index = 0;
json.objects.forEach(function(obj) {
    setTimeout(function(){
        console.log('foobar');
        self.insertDesignJsonObject(obj, index);
    }, 5000);
});
24
Goose

Jasonが言ったことは彼の答えでは完全に正しいが、私はそれをより明確にするために試してみると思った。

これは実際には古典的な閉鎖の問題です。通常、次のようになります。

for(var i = 0; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    },i * 1000)
}

初心者はコンソールに以下が表示されることを期待します。

0
(0 seconds pass...)
1
(1 second passes...)
2
(etc..)

しかし、そうではありません!実際に表示されるのは番号10 10回記録されます(1秒間に1回)!

「なぜそれが起こるのですか?」いい質問ですね。閉鎖範囲。上記のforループにはクロージャスコープがありません。なぜならJavaScriptでは、関数(ラムダ)だけがクロージャスコープを持つからです!

参照: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

しかしながら!これを試みた場合、あなたの試みは望ましい出力を達成したでしょう:

    json.objects.forEach(function(obj,index,collection) {
        setTimeout(function(){
            console.log('foobar');
            self.insertDesignJsonObject(obj, index);
        }, index * 5000);
    });

"closur-ed" index変数にアクセスできるため、関数(ラムダ)が呼び出されたときに、その状態が期待される状態であることに依存できます。

その他のリソース:

JavaScriptクロージャーの仕組み

http://javascript.info/tutorial/closures

http://code.tutsplus.com/tutorials/closures-front-to-back--net-24869

28
The Dembinski

setTimeout はayncです。それが行うことは、registerコールバック関数であり、遅延後にトリガーされるバックグラウンドに配置します。 forEachは同期関数です。あなたのコードは、コールバックを「一度に」登録し、それぞれが5秒後にトリガーされるようにしました。

それを避ける2つの方法:

タイマーを設定するためのインデックスがあります。

json.objects.forEach(function(obj, index) {
    setTimeout(function(){
      // do whatever
    }, 5000 * (index + 1));
});

このように、遅延係数はオブジェクトのインデックスに基づいているため、同時に登録した場合でも、独自の遅延に基づいてトリガーされます。 index + 1は0から始まるため、問題の結果と同じ結果を保持します。

setIntervalでオブジェクトをループします

var i = 0;
var interval = setInterval(function(){
    var obj = json.objects[i];
    // do whatever
    i++;
    if(i === json.objects.length) clearInterval(interval);
}, 5000);

setInterval はsetTimeoutに似ていますが、間隔に基づいて定期的にトリガーされます。ここでは、オブジェクトにアクセスし、間隔関数のインデックスinsideを更新します。また、最後に間隔をクリアすることを忘れないでください。

これらの2つの違いは、setIntervalが1つの関数のみを登録し、リスト内のアイテムの数だけ登録されたsetTimeoutと比較されることです。

15
Mengo