SetTimeout не работает внутри forEach

У меня есть forEach, который вызывает функцию. Между каждым вызовом должна быть задержка. Я положил его внутри setTimeout внутри forEach. Это не соответствует таймауту после первого ожидания. Вместо этого он ждет один раз, а затем запускает все сразу. Я установил тайм-аут на 5 секунд, и я использую консоль для подтверждения. 5 секунд ожидания, затем несколько журналов консоли foobar все сразу.

Почему я получаю это поведение?

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

Ответ 1

То, что сказал Джейсон, совершенно правильно в его ответе, но я подумал, что сделаю это, чтобы лучше разъяснить.

Это фактически классическая проблема закрытия. Обычно это выглядит примерно так:

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 раз в секунду)!

"Почему это происходит?" Отличный вопрос. Закрытие. В цикле for выше отсутствует область ограничения, поскольку в javascript только функции (lambdas) имеют ограниченную область действия!

См. 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);
    });

Поскольку у вас есть доступ к "закрытой" index переменной, вы можете полагаться на состояние, являющееся ожидаемым, когда функция (лямбда) вызывается!

Другие источники:

Как работают блокировки JavaScript?

http://javascript.info/tutorial/closures

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

Ответ 2

setTimeout - aync. Он выполняет регистрацию функции обратного вызова и помещает ее в фоновый режим, который будет запускаться после задержки. В то время как forEach является синхронной функцией. Итак, что ваш код сделал, это обратные вызовы "все сразу", каждый из которых будет запущен через 5 секунд.

Два способа избежать этого:

Имейте указатель, чтобы установить таймер.

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, хотя он периодически запускается на основе интервала. Здесь мы обращаемся к объекту и обновляем индекс внутри функции интервала. Также не забудьте очистить интервал в конце.

Разница между этими двумя параметрами: SetInterval регистрирует только одну функцию по сравнению с setTimeout, зарегистрированным как количество элементов в списке.