Как добавить задержку в цикле JavaScript?
Только первый сценарий верно: после того, как показывает alert(‘hi’) , он будет ждать в течение 3 секунд, а затем alert(‘hello’) будет отображаться, но затем alert(‘hello’) будет повторно постоянно. Мне хотелось бы, чтобы после alert(‘hello’) показано через 3 секунды после alert(‘hi’) тогда ему нужно ждать 3 секунды для второго alert(‘hello’) и так далее.
23 ответа
Функция setTimeout() не блокирует и немедленно вернется. Поэтому ваш цикл будет очень быстро итеративно, и он будет запускать три-трижды триггеры таймаута один за другим в быстрой последовательности. Вот почему ваши первые предупреждения появляются через 3 секунды, а все остальные следуют последовательно без каких-либо задержек. Вместо этого вы можете использовать что-то вроде этого:
var i = 1; // set your counter to 1 function myLoop () < // create a loop function setTimeout(function () < // call a 3s setTimeout when the loop is called alert('hello'); // your code here i++; // increment the counter if (i < 10) < // if the counter < 10, call the loop function myLoop(); // .. again which will trigger another >// .. setTimeout() >, 3000) > myLoop(); // start the loop
Вы могли бы также аккуратно его использовать, используя функцию self invoking, передавая количество итераций в качестве аргумента:
(function myLoop (i) < setTimeout(function () < alert('hello'); // your code here if (--i) myLoop(i); // decrement i and call myLoop again if i >0 >, 3000) >)(10); // pass the number of iterations as an argument
Разве использование рекурсии для реализации этого не приведет к переполнению стека? Если бы вы хотели сделать миллион итераций, что было бы лучшим способом реализовать это? Может быть, setInterval, а затем очистить его, как решение Абеля ниже?
Я заметил, что это тоже работает, если вы откладываете выполнение функций в цикле. Я пытался установить функцию TimeTimeout внутри цикла for, но она не передавала ни одной из локальных переменных.
Рекурсия не очень хороша, если вы хотите сделать много циклов . Я обнаружил, что это замедляется на нескольких тысячах итераций.
@Adam: Adam: насколько я понимаю, так как setTimeout не является блокирующим, это не повторение — стек-окно закрывается после каждого setTimeout и остается только один setTimeout, ожидающий выполнения . Верно?
Я предполагаю, что время ожидания будет продолжать существовать даже после того, как я достигну 0. Я думаю, что вы захотите включить оператор else с clearTimeout, чтобы как-то остановить setTimeout.
@joey Вы путаете setTimeout с setInterval . Тайм-ауты неявно уничтожаются при вызове обратного вызова.
@Adam Вы говорите о рекурсии, вызывающей переполнение стека на stackoverflow.com. Вы выиграли интернет!
Попробуйте что-то вроде этого:
var i = 0, howManyTimes = 10; function f() < alert( "hi" ); i++; if( i < howManyTimes )< setTimeout( f, 3000 ); >> f();
Если вы используете ES6, вы можете использовать let для этого:
То, что let делает, объявляет i для каждой итерации, а не цикла. Таким образом, то, что передается setTimeout , именно то, что мы хотим.
Поблагодарить! Я бы не подумал об этом методе самостоятельно. Актуальная область видимости. Представь это.
Я считаю, что это имеет те же проблемы выделения памяти, что и ответ, описанный в stackoverflow.com/a/3583795/1337392
Вызов setTimeout синхронно вычисляет значение аргумента i*3000 внутри цикла и передает его в setTimeout по значению. Использование let является необязательным и не связано с вопросами и ответами.
Другой способ — умножить время на таймаут, но обратите внимание, что это не похоже на sleep. Код после того, как цикл будет выполнен немедленно, выполняется только выполнение функции обратного вызова.
for (var start = 1; start < 10; start++) setTimeout(function () < alert('hello'); >, 3000 * start);
Первый тайм-аут будет установлен на 3000 * 1 , второй — на 3000 * 2 и т.д.
Стоит отметить, что вы не можете надежно использовать start внутри своей функции, используя этот метод.
Почему это плохая практика и почему возникают проблемы с выделением памяти? Этот ответ переносит те же проблемы? stackoverflow.com/a/36018502/1337392
@Flame_Phoenix Flame_Phoenix это плохая практика, потому что программа будет держать один таймер для каждого цикла, при этом все таймеры будут работать одновременно. Таким образом, если будет 1000 итераций, в начале будет одновременно работать 1000 таймеров.
Начиная с ES7, есть лучший способ ожидания цикла:
function timer(ms) < return new Promise(res =>setTimeout(res, ms)); > async function load () < for (var i = 0; i < 3; i++) < console.log(i); await timer(3000); >> load();
Обратите внимание, что ES7 сегодня редко поддерживается, поэтому вам нужно использовать его вместе с Babel.
Он отлично работает для меня. Я просто хочу спросить, что если я хочу разорвать цикл, как я могу сделать это при использовании await?
Я думаю, вам нужно что-то вроде этого:
var TimedQueue = function(defaultDelay)< this.queue = []; this.index = 0; this.defaultDelay = defaultDelay || 3000; >; TimedQueue.prototype = < add: function(fn, delay)< this.queue.push(< fn: fn, delay: delay >); >, run: function(index)< (index || index === 0) && (this.index = index); this.next(); >, next: function()< var self = this , i = this.index++ , at = this.queue[i] , next = this.queue[this.index] if(!at) return; at.fn(); next && setTimeout(function()< self.next(); >, next.delay||this.defaultDelay); >, reset: function() < this.index = 0; >>
var now = +new Date(); var x = new TimedQueue(2000); x.add(function()< console.log('hey'); console.log(+new Date() - now); >); x.add(function()< console.log('ho'); console.log(+new Date() - now); >, 3000); x.add(function()< console.log('bye'); console.log(+new Date() - now); >); x.run();
Примечание: использование предупреждений закрывает выполнение javascript до закрытия предупреждения. Это может быть больше кода, чем вы просили, но это надежное многоразовое решение.
единственное, что я говорю, я взломал таким образом, использовал $.Deferred но это был какой-то другой сценарий, чтобы все $.Deferred , превью! .
Я бы использовал setInteval . Как это,
var period = 1000; // ms var endTime = 10000; // ms var counter = 0; var sleepyAlert = setInterval(function() < alert('Hello'); if(counter === endTime)< clearInterval(sleepyAlert); >counter += period; >, period);
Я немного погуглил и ничего не нашел, почему setInterval плохой? Можете ли вы дать нам ссылку? или пример? Спасибо
Я думаю, дело в том, что SetInterval() продолжает порождать «потоки» даже в случае какой-либо ошибки или блока.
В ES6 (ECMAScript 2015) вы можете выполнять итерацию с задержкой generator и интервал.
Генераторы, новая функция ECMAScript 6, являются функциями, которые могут быть приостановлен и возобновлен. Вызов genFunc не выполняет его. Вместо этого возвращает так называемый объект-генератор, который позволяет нам управлять genFuncs выполнение. genFunc() первоначально приостанавливается в начале его тело. Метод genObj.next() продолжает выполнение genFunc, до следующего урожая. (Изучение ES6)
Пример кода:
let arr = [1, 2, 3, 'b']; let genObj = genFunc(); let val = genObj.next(); console.log(val.value); let interval = setInterval(() => < val = genObj.next(); if (val.done) < clearInterval(interval); >else < console.log(val.value); >>, 1000); function* genFunc() < for(let item of arr) < yield item; >>
Итак, если вы используете ES6, это самый элегантный способ добиться цикла с задержкой (по моему мнению).
Я делаю это с Bluebirds Promise.delay и рекурсией.
function myLoop(i) < return Promise.delay(1000) .then(function() < if (i >0) < alert('hello'); return myLoop(i -= 1); >>); > myLoop(3);
Просто подумал, что я разместил здесь свои два цента. Эта функция выполняет итеративный цикл с задержкой. См. этот jsfiddle. Функция следующая:
function timeout(range, time, callback) < var i = range[0]; callback(i); Loop(); function Loop()< setTimeout(function()< i++; if (i>, time*1000) > >
//This function prints the loop number every second timeout([0, 5], 1, function(i)< console.log(i); >);
//This function prints the loop number instantly for (var i = 0; i
Вы можете использовать RxJS оператор интервала. Интервал испускает целое число каждые x секунд, а take указывает количество раз, когда он должен испускать числа
Rx.Observable .interval(1000) .take(10) .subscribe((x) => console.log(x))
var startIndex = 0; var data = [1, 2, 3]; var timeout = 1000; function functionToRun(i, length) < alert(data[i]); >(function forWithDelay(i, length, fn, delay) < setTimeout(function() < fn(i, length); i++; if (i < length) < forWithDelay(i, length, fn, delay); >>, delay); >)(startIndex, data.length, functionToRun, timeout);
Измененная версия ответа Даниэля Вассалло с переменными, извлекаемыми в параметры, чтобы сделать функцию более многоразовой:
Сначала определим некоторые существенные переменные:
var startIndex = 0; var data = [1, 2, 3]; var timeout = 3000;
Затем вы должны определить функцию, которую вы хотите запустить. Это будет передано i, текущий индекс цикла и длина цикла, если он вам понадобится:
function functionToRun(i, length)
Самозаверяемая версия
(function forWithDelay(i, length, fn, delay) < setTimeout(function () < fn(i, length); i++; if (i < length) < forWithDelay(i, length, fn, delay); >>, delay); >)(startIndex, data.length, functionToRun, timeout);
Функциональная версия
function forWithDelay(i, length, fn, delay) < setTimeout(function () < fn(i, length); i++; if (i < length) < forWithDelay(i, length, fn, delay); >>, delay); > forWithDelay(startIndex, data.length, functionToRun, timeout); // Lets run it
Простая реализация показа фрагмента текста каждые две секунды, пока работает цикл.
for (var i = 0; i < foo.length; i++) < setInterval(function()< console.log("I will appear every 2 seconds"); >, 2000); break; >;
var timer, i = 10; function myLoop () < // create a loop function timer = setTimeout(function () < document.getElementById("demo").innerHTML = i; i--; if (i >= 0) < myLoop(); >else < clearTimeout(timer); // clear timeout document.getElementById("demo").innerHTML = "DOARRRR .."; >>, 1000); > myLoop();
Пожалуйста, всегда предоставляйте хотя бы краткое описание своих фрагментов кода, по крайней мере, чтобы другие были уверены, что вы ответите на этот вопрос.
Ответы, содержащие только код, не поощряются, так как они не предоставляют много информации для будущих читателей. Пожалуйста, дайте некоторое объяснение тому, что вы написали.
для общего использования «забудьте обычные циклы», и эта комбинация «setInterval» включает в себя «setTimeOut»: как это (из моих реальных задач).
function iAsk(lvl)< var i=0; var intr =setInterval(function()< // start the loop i++; // increment it if(i>lvl) < // check if the end round reached. clearInterval(intr); return; >setTimeout(function()< $(".imag").prop("src",pPng); // do first bla bla bla after 50 millisecond >,50); setTimeout(function()< // do another bla bla bla after 100 millisecond. seq[i-1]=(Math.ceil(Math.random()*4)).toString(); $("#hh").after('
'+i + ' : rand= '+(Math.ceil(Math.random()*4)).toString()+' > '+seq[i-1]); $("#d"+seq[i-1]).prop("src",pGif); var d =document.getElementById('aud'); d.play(); >,100); setTimeout(function()< // keep adding bla bla bla till you done :) $("#d"+seq[i-1]).prop("src",pPng); >,900); >,1000); // loop waiting time must be >= 900 (biggest timeOut for inside actions) >
PS: Поймите, что реальное поведение (setTimeOut): все они начнутся в одно и то же время «три bla bla bla начнут отсчитывать в тот же момент», поэтому сделайте другой тайм-аут, чтобы организовать выполнение.
PS 2: пример цикла синхронизации, но для циклов реакции вы можете использовать события, обещая асинхронное ожидание.
Вот как я создал бесконечный цикл с задержкой, которая ломается при определенном условии:
// Now continuously check the app status until it completed, // failed or times out. The isFinished() will throw exception if // there is a failure. while (true) < let status = await this.api.getStatus(appId); if (isFinished(status)) < break; >else < // Delay before running the next loop iteration: await new Promise(resolve =>setTimeout(resolve, 3000)); > >
Ключевым моментом здесь является создание нового обещания, которое разрешается с помощью тайм-аута и ждать его разрешения.
Очевидно, вам нужна поддержка async/await. Работает в Node 8.
Этот script работает для большинства вещей
function timer(start) < setTimeout(function () < //The timer alert('hello'); >, start*3000); //needs the "start*" or else all the timers will run at 3000ms > for(var start = 1; start
Вот функция, которую я использую для циклического перемещения по массиву:
function loopOnArrayWithDelay(theArray, delayAmount, i, theFunction, onComplete)< if (i < theArray.length && typeof delayAmount == 'number')< console.log("i "+i); theFunction(theArray[i], i); setTimeout(function()< loopOnArrayWithDelay(theArray, delayAmount, (i+1), theFunction, onComplete)>, delayAmount); >else < onComplete(i); >>
Вы используете его следующим образом:
loopOnArrayWithDelay(YourArray, 1000, 0, function(e, i)< //Do something with item >, function(i) < //Do something once loop has completed >
/* Use Recursive and setTimeout call below function will run loop loopFunctionNeedCheck until conditionCheckAfterRunFn = true, if conditionCheckAfterRunFn == false : delay reRunAfterMs miliseconds and continue loop tested code, thanks */ function functionRepeatUntilConditionTrue(reRunAfterMs, conditionCheckAfterRunFn, loopFunctionNeedCheck) < loopFunctionNeedCheck(); var result = conditionCheckAfterRunFn(); //check after run if (!result) < setTimeout(function () < functionRepeatUntilConditionTrue(reRunAfterMs, conditionCheckAfterRunFn, loopFunctionNeedCheck) >, reRunAfterMs); > else console.log("completed, thanks"); //if you need call a function after completed add code call callback in here > //passing-parameters-to-a-callback-function // From Prototype.js if (!Function.prototype.bind) < // check if native implementation available Function.prototype.bind = function () < var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift(); return function () < return fn.apply(object, args.concat(Array.prototype.slice.call(arguments))); >; >; > //test code: var result = 0; console.log("---> init result is " + result); var functionNeedRun = function (step) < result+=step; console.log("current result is " + result); >var checkResultFunction = function () < return result==100; >//call this function will run loop functionNeedRun and delay 500 miliseconds until result=100 functionRepeatUntilConditionTrue(500, checkResultFunction , functionNeedRun.bind(null, 5)); //result log from console: /* ---> init result is 0 current result is 5 undefined current result is 10 current result is 15 current result is 20 current result is 25 current result is 30 current result is 35 current result is 40 current result is 45 current result is 50 current result is 55 current result is 60 current result is 65 current result is 70 current result is 75 current result is 80 current result is 85 current result is 90 current result is 95 current result is 100 completed, thanks */
var icount=0; for (let i in items) < icount=icount+1000; new beginCount(items[i],icount); >function beginCount(item,icount)< setTimeout(function () < new actualFunction(item,icount); >, icount); > function actualFunction(item,icount) < //. runs ever 1 second console.log(icount); >