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, зарегистрированным как количество элементов в списке.