Як додати затримку в циклі 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 секунди, а всі інші подаються послідовно без жодної затримки.

Ви можете замість цього використати щось подібне:

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
Чи не використовуються рекурсії для його реалізації, в результаті переповнення стека може бути переповнене? Якби ви хотіли зробити мільйон ітерацій, який би кращий спосіб здійснити це? Може, встановіть Інтервал і потім очистіть його, як рішення Абеля нижче?
Адам

7
@Adam: моє розуміння полягає в тому, що оскільки setTimeout не блокує, це не є рекусією - стек-вікно закривається після кожного setTimeout, і існує лише один setTimeout, який очікує на виконання ... Так?
Джо

3
Як би це працювало при ітерації об'єкта, як for inцикл?
vsync

1
@vsync ПодивітьсяObject.keys()
Braden Best

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 Які проблеми з розподілом пам'яті?
4castle

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, щоб він працював скрізь.


Це прекрасно працює для мене. Я просто хочу запитати, що якщо я хочу перервати цикл, як я можу це зробити під час використання wait?
Сахін Шах

@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але це був якийсь інший сценарій, щоб він працював, великі пальці ..!
ArifMustafa

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 it, і ви дізнаєтесь
Airy

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 - це вказати кількість разів, яку воно має випромінювати numberss

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

приємний і як я можу передавати дані у функцію без глобальної змінної
Sundara Prabu

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
Нгуен Ба Дань - FAIC HN

Навіщо імпортувати jQuery?
Еліас Соарес

Вибачте ... це зайве .. хе. Я не знаю вмісту публікації ... цей перший.
Нгуен Ба Дан - ФАЙК НН

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 / очікування. Працює у вузлі 8.


0

для загального використання "забути нормальні петлі", а використання цього поєднання "setInterval" включає "setTimeOut" s: як це (з моїх реальних завдань).

        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
Відповіді на код не рекомендуються, оскільки вони не надають багато інформації майбутнім читачам, будь ласка, надайте пояснення тому, що ви написали
WhatsThePoint

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.