Как добавить задержку в цикле JavaScript?


346

Я хотел бы добавить задержку / сон внутри whileцикла:

Я попробовал это так:

alert('hi');

for(var start = 1; start < 10; start++) {
  setTimeout(function () {
    alert('hello');
  }, 3000);
}

Только первый сценарий верен: после показа alert('hi')он будет ждать 3 секунды, затем alert('hello')будет отображаться, но затем alert('hello')будет повторяться постоянно.

Я хотел бы, чтобы после alert('hello')3 секунд показывалось, после alert('hi')этого нужно подождать 3 секунды во второй раз alert('hello')и так далее.

Ответы:


750

setTimeout()Функция неблокирующая и немедленно вернуться. Поэтому ваш цикл будет повторяться очень быстро, и он будет запускать 3-секундные триггеры таймаута один за другим в быстрой последовательности. Вот почему ваши первые оповещения появляются через 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
    console.log('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

Вы также можете сделать это, используя функцию, вызывающую саму себя, передавая число итераций в качестве аргумента:

(function myLoop(i) {
  setTimeout(function() {
    console.log('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


27
Разве использование рекурсии для реализации этого не приведет к переполнению стека? Если бы вы хотели сделать миллион итераций, что было бы лучшим способом реализовать это? Может быть, setInterval, а затем очистить его, как решение Абеля ниже?
Адам

7
@ Adam: насколько я понимаю, так как setTimeout не является блокирующим, то это не повторение - стек-окно закрывается после каждого setTimeout, и всегда остается только один setTimeout, ожидающий выполнения ... Верно?
Джо

3
Как это будет работать при переборе объектов типа for inцикла?
vsync

1
@vsync Посмотрите наObject.keys()
Брэден Бест

1
@joey Ты путаешь setTimeoutс setInterval. Тайм-ауты неявно уничтожаются при вызове обратного вызова.
cdhowie

72

Попробуйте что-то вроде этого:

var i = 0, howManyTimes = 10;
function f() {
    alert( "hi" );
    i++;
    if( i < howManyTimes ){
        setTimeout( f, 3000 );
    }
}
f();

69

Если вы используете ES6, вы можете использовать letдля этого:

for (let i=1; i<10; i++) {
    setTimeout( function timer(){
        alert("hello world");
    }, i*3000 );
}

Что letобъявляется iдля каждой итерации , а не цикла. Таким образом, то, что передается setTimeoutименно то, что мы хотим.


1
Спасибо! Я бы не подумал об этом методе самостоятельно. Актуальная область видимости. Представь, что ...
София Голд

1
Я считаю, что это имеет те же проблемы выделения памяти, что и ответ, описанный в stackoverflow.com/a/3583795/1337392
Flame_Phoenix

1
@Flame_Phoenix Какие проблемы с выделением памяти?
4каста

1
Вызов setTimeout синхронно вычисляет значение i*3000аргумента внутри цикла и передает его setTimeoutпо значению. Использование не letявляется обязательным и не связано с вопросом и ответом.
traktor53

@Flame_Phoenix упомянул, что в этом коде есть проблемы. По сути, при первом проходе вы создаете таймер, а затем немедленно повторяете цикл снова и снова до завершения цикла по условию ( i < 10), так что у вас будет несколько параллельных таймеров, которые создают распределение памяти, и это хуже при большем количестве итераций.
XCanG

63

Начиная с ES7, есть лучший способ ожидания цикла:

// Returns a Promise that resolves after "ms" Milliseconds
function timer(ms) {
 return new Promise(res => setTimeout(res, ms));
}

async function load () { // We need to wrap the loop into an async function for this to work
  for (var i = 0; i < 3; i++) {
    console.log(i);
    await timer(3000); // then the created Promise can be awaited
  }
}

load();

Когда двигатель достигает awaitдетали, он устанавливает тайм-аут и останавливает выполнениеasync function . Затем, когда время ожидания истекает, выполнение продолжается в этот момент. Это весьма полезно, поскольку вы можете отложить (1) вложенные циклы, (2) условно, (3) вложенные функции:

async function task(i) { // 3
  await timer(1000);
  console.log(`Task ${i} done!`);
}

async function main() {
  for(let i = 0; i < 100; i+= 10) {
    for(let j = 0; j < 10; j++) { // 1
      if(j % 2) { // 2
        await task(i + j);
      }
    }
  }
}
    
main();

function timer(ms) { return new Promise(res => setTimeout(res, ms)); }

Ссылка на MDN

Хотя ES7 теперь поддерживается NodeJS и современными браузерами, вы можете захотеть перенести его с BabelJS, чтобы он работал везде.


Он отлично работает для меня. Я просто хочу спросить, что если я хочу разорвать цикл, как я могу сделать это при использовании await?
Сачин Шах

@ Sachin break;может быть?
Джонас Уилмс,

Спасибо за это решение. Приятно использовать все существующие управляющие структуры и не нужно выдумывать продолжения.
Гас

Это все равно будет просто создавать различные таймеры, и они будут разрешаться в разное время, а не в последовательности?
Дэвид Йелл

@JonasWilms Кажется, я полностью пропустил кнопку «Запустить фрагмент»: facepalm:
Дэвид Йелл

24

Другой способ - увеличить время ожидания, но учтите, что это не похоже на сон . Код после цикла будет выполнен немедленно, только выполнение функции обратного вызова откладывается.

for (var start = 1; start < 10; start++)
    setTimeout(function () { alert('hello');  }, 3000 * start);

Первый таймаут будет установлен на 3000 * 1, второй на 3000 * 2и так далее.


2
Стоит отметить, что вы не можете надежно использовать startвнутри своей функции, используя этот метод.
DBS

2
Плохая практика - ненужное распределение памяти.
Александр Трахименок

Призыв к творчеству, но это чертовски плохая практика. :)
Саливан

2
Почему это плохая практика и почему возникают проблемы с выделением памяти? Этот ответ переносит те же проблемы? stackoverflow.com/a/36018502/1337392
Flame_Phoenix

1
@ Flame_Phoenix это плохая практика, потому что программа будет держать один таймер для каждого цикла, при этом все таймеры будут работать одновременно. Таким образом, если будет 1000 итераций, в начале будет одновременно работать 1000 таймеров.
Иоаким

16

Это будет работать

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

Попробуйте эту скрипку: https://jsfiddle.net/wgdx8zqq/


1
Это действительно запускает все вызовы тайм-аута примерно в одно и то же время
Эдди

Единственное, что я говорю, я взломал этот способ, использовал, $.Deferredно это был какой-то другой сценарий, чтобы это сработало, превью! ..!
АрифМустафа,

15

Я думаю, вам нужно что-то вроде этого:

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, пока вы не закроете предупреждение. Это может быть больше кода, чем вы просили, но это надежное решение для повторного использования.


15

Я бы наверное использовал 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);

3
SetTimeout намного лучше, чем отстойный интервал. Google, и вы будете знать
Эйри

14
Я немного погуглил и ничего не нашел, почему setInterval плохой? Можете ли вы дать нам ссылку? или пример? Спасибо
Marcs

Я думаю, дело в том, что SetInterval()порождает «потоки» даже в случае какой-либо ошибки или блока.
Матин Улхак

8

В ES6 (ECMAScript 2015) вы можете выполнять итерацию с задержкой с генератором и интервалом.

Генераторы, новая функция ECMAScript 6, являются функциями, которые можно приостанавливать и возобновлять. Вызов genFunc не выполняет его. Вместо этого он возвращает так называемый генераторный объект, который позволяет нам контролировать выполнение genFunc. 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, это самый элегантный способ добиться цикла с задержкой (на мой взгляд).


4

Вы можете использовать оператор интервала RxJS . Интервал выдает целое число каждые x секунд, а значение take указывает, сколько раз он должен выдавать числа

Rx.Observable
  .interval(1000)
  .take(10)
  .subscribe((x) => console.log(x))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.lite.min.js"></script>


4

Просто подумал, что выложу здесь свои два цента. Эта функция запускает итерационный цикл с задержкой. Смотрите это jsfiddle . Функция выглядит следующим образом:

function timeout(range, time, callback){
    var i = range[0];                
    callback(i);
    Loop();
    function Loop(){
        setTimeout(function(){
            i++;
            if (i<range[1]){
                callback(i);
                Loop();
            }
        }, 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<5; i++){
    console.log(i);
}

4

Я делаю это с помощью Bluebird's Promise.delayи рекурсии.

function myLoop(i) {
  return Promise.delay(1000)
    .then(function() {
      if (i > 0) {
        alert('hello');
        return myLoop(i -= 1);
      }
    });
}

myLoop(3);
<script src="//cdnjs.cloudflare.com/ajax/libs/bluebird/2.9.4/bluebird.min.js"></script>


2

В ES6 вы можете сделать следующее:

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

В ES5 вы можете сделать как:

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

Причина в том, что letвы можете объявлять переменные, которые ограничены областью действия оператора блока или выражением, в котором он используется, в отличие от varключевого слова, которое определяет переменную глобально или локально для всей функции независимо от области действия блока.


1

Модифицированная версия ответа Даниэля Вассалло с переменными, извлеченными в параметры, чтобы сделать функцию более пригодной для повторного использования:

Сначала давайте определим некоторые существенные переменные:

var startIndex = 0;
var data = [1, 2, 3];
var timeout = 3000;

Далее вы должны определить функцию, которую хотите запустить. Это будет передано i, текущий индекс цикла и длина цикла, в случае, если вам это нужно:

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);

Функциональная версия

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

хороший и как я могу передать данные в функцию без глобальной переменной
Сундара

1
   let counter =1;
   for(let item in items) {
        counter++;
        setTimeout(()=>{
          //your code
        },counter*5000); //5Sec delay between each iteration
    }

1

Ты делаешь это:

console.log('hi')
let start = 1
setTimeout(function(){
  let interval = setInterval(function(){
    if(start == 10) clearInterval(interval)
    start++
    console.log('hello')
  }, 3000)
}, 3000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


лучше использовать журналы консоли вместо оповещений, было не очень весело закрывать оповещения на полминуты;)
Хендри

Да. Я вижу! Но просьба настороженная ... huz
Нгуен Ба

Зачем импортировать jQuery?
Элиас Соарес

Извините ... это ненужно .. хе. Я не знаю содержание поста ... это первое.
Нгуен Ба Данх - FAIC HN

0
/* 
  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
*/

7
Имена ваших функций ужасны, это главная причина, по которой этот код так трудно читать.
Марк Уолтерс

0

Вот как я создал бесконечный цикл с задержкой, которая прерывается на определенное условие:

  // Now continuously check the app status until it's 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 для этого. Работает в Узле 8.


0

для обычного использования "забудь нормальные циклы" и используй эту комбинацию "setInterval" включает в себя "setTimeOut": like this (из моих реальных задач).

        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('<br>'+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): они все начнутся в одно и то же время «три бла-бла-бла начнут обратный отсчет в один и тот же момент», поэтому сделайте другой тайм-аут, чтобы организовать выполнение.

PS 2: пример для цикла синхронизации, но для циклов реакции вы можете использовать события, обещают асинхронное ожидание ..


0

<!DOCTYPE html>
<html>
<body>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
    for(var i=0; i<5; i++) {
    	var sno = i+1;
       	(function myLoop (i) {          
             setTimeout(function () {   
             	alert(i); // Do your function here 
             }, 1000*i);
        })(sno);
    }
}
</script>

</body>
</html>


1
Пожалуйста, всегда предоставляйте хотя бы краткое описание своих фрагментов кода, по крайней мере, чтобы другие были уверены, что вы ответите на этот вопрос.
Hexfire

1
Ответы, содержащие только код, не поощряются, так как они не предоставляют много информации для будущих читателей. Пожалуйста,
дайте

0

Насколько мне известно, setTimeoutфункция вызывается асинхронно. Что вы можете сделать, это обернуть весь цикл в асинхронную функцию и ждать, Promiseчто содержит setTimeout, как показано:

var looper = async function () {
  for (var start = 1; start < 10; start++) {
    await new Promise(function (resolve, reject) {
      setTimeout(function () {
        console.log("iteration: " + start.toString());
        resolve(true);
      }, 1000);
    });
  }
  return true;
}

И тогда вы звоните запустить его так:

looper().then(function(){
  console.log("DONE!")
});

Пожалуйста, найдите время, чтобы получить хорошее представление об асинхронном программировании.


0

Просто попробуйте это

 var arr = ['A','B','C'];
 (function customLoop (arr, i) {
    setTimeout(function () {
    // Do here what you want to do.......
    console.log(arr[i]);
    if (--i) {                
      customLoop(arr, i); 
    }
  }, 2000);
})(arr, arr.length);

результат

A // after 2s
B // after 2s
C // after 2s

-1

Вот функция, которую я использую для цикла по массиву:

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
}

-1

Этот скрипт работает для большинства вещей

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 < 10; start++) {
    timer(start);
}

-1

Попробуй это...

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);
}

-1

Простая реализация показа фрагмента текста каждые две секунды, пока работает цикл.

for (var i = 0; i < foo.length; i++) {
   setInterval(function(){ 
     console.log("I will appear every 2 seconds"); 
   }, 2000);
  break;
};

-3

Попробуй это

//the code will execute in 1 3 5 7 9 seconds later
function exec(){
  for(var i=0;i<5;i++){
   setTimeout(function(){
     console.log(new Date());   //It's you code
   },(i+i+1)*1000);
  }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.