如何在JavaScript循环中添加延迟?


345

我想在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'),依此类推。

Answers:


749

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


26
最终会不会使用递归来实现此目标?如果您想进行一百万次迭代,哪种更好的方法来实现呢?也许先设置setInterval然后再清除它,就像下面的Abel解决方案一样?
2014年

7
@Adam:我的理解是,由于setTimeout是非阻塞的,所以这不是回避-每次setTimeout之后堆栈窗口都会关闭,并且只有一个setTimeout等待执行...对吗?
2015年

3
for in循环这样的对象迭代时,这将如何工作?
vsync 2015年

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在循环内部同步计算参数的值,并将其传递给setTimeoutvalue。的使用let是可选的,与问题和答案无关。
traktor53 '18

@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的参考

虽然NodeJS和现代浏览器现在都支持ES7,但您可能希望将其与BabelJS一起转换,以便它可以在任何地方运行。


这对我来说可以。我只想问一下是否要中断循环,使用await时该如何做?
萨钦沙(Shachin Shah),

@sachin break;也许?
乔纳斯·威尔姆斯

感谢您的解决方案。使用所有现有的控制结构并且无需发明连续性是很好的。
古斯(Gus)

这仍然会创建各种计时器,它们将在不同的时间而不是按顺序解决吗?
David Yell

@大卫嗯不?您是否实际运行过代码?
乔纳斯·威尔姆斯

24

另一种方法是将超时时间加倍,但是请注意,这不像sleep。循环后的代码将立即执行,仅推迟执行回调函数。

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

第一个超时将设置为3000 * 1,第二个设置为3000 * 2依此类推。


2
值得指出的是,start使用这种方法无法可靠地在函数内部使用。
DBS 2015年

2
不良做法-不必要的内存分配。
Alexander Trakhimenok 2015年

支持创造力,但这是糟糕的做法。:)
Salivan

2
为什么这是一种不好的做法,为什么会有内存分配问题?这个答案会遇到同样的问题吗?stackoverflow.com/a/36018502/1337392
Flame_Phoenix

1
@Flame_Phoenix是一个不好的做法,因为该程序将为每个循环保留一个计时器,所有计时器同时运行。因此,如果有1000次迭代,则开始时将同时运行1000个计时器。
约阿基姆(Joakim)'18年

16

这会工作

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

试试这个小提琴:https : //jsfiddle.net/wgdx8zqq/


1
但是,这确实会触发几乎所有的超时呼叫
Eddie

我唯一说的是,我已经用这种方式破解了,习惯了,$.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比settinterval好得多。谷歌它,你会知道
艾里2014年

14
我用谷歌搜索了一下,却一无所获,为什么setInterval不好?你能给我们一个链接吗?还是一个例子?谢谢
榨渣

想说的是,SetInterval()即使在发生某些错误或阻塞的情况下,仍会继续产生“线程”。
Mateen Ulhaq '17

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

原因是,letvar关键字不同,关键字可以全局地定义变量,或者在整个函数中局部定义,而不管块范围如何,因此允许您声明限于块语句或使用它的表达式的范围的变量。


1

丹尼尔·瓦萨洛(Daniel Vassallo)答案的修改版,其中将变量提取到参数中,以使函数更可重用:

首先让我们定义一些基本变量:

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>


最好使用控制台日志而不是警报,关闭警报半分钟不是很有趣;)
Hendry

是的 我知道了!但是请求很警惕... huz
Nguyen Ba Danh-FAIC HN 19'Aug

为什么要导入jQuery?
伊莱亚斯·苏亚雷斯

对不起...没必要..呵呵。我不知道发布内容...首先。
Nguyen Ba Danh-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));
    }
  }

此处的关键是创建一个新的Promise,该Promise通过超时进行解析,并等待其解决。

显然,您需要为此提供异步/等待支持。在节点8中工作。


0

通常使用“忘记正常循环”,并使用“ 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('<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)的真实行为:它们都将在同一时间开始“三个bla bla bla将在同一时刻开始递减计数”,因此请设置不同的超时时间来安排执行。

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函数被异步调用。您可以做的是将整个循环包装在async函数中,然后等待Promise包含setTimeout的a ,如下所示:

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);
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.