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

oli*_*dev 313 javascript sleep loops

我想在while循环中添加延迟/睡眠:

我试过这样的:

alert('hi');

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

只有第一种情况是真的:显示后alert('hi'),它将等待3秒然后alert('hello')将显示,但随后alert('hello')将反复不断.

我想要的是,在alert('hello')3秒之后显示alert('hi')之后,它需要等待第二次3秒alert('hello'),依此类推.

Dan*_*llo 705

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
      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
Run Code Online (Sandbox Code Playgroud)

您也可以通过使用自调用函数将迭代次数作为参数传递来消除它:

(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
Run Code Online (Sandbox Code Playgroud)

  • 不会使用递归来实现这最终会受到堆栈溢出的影响吗?如果你想做一百万次迭代,那么实现它的更好的方法是什么?也许setInterval然后清除它,就像下面的Abel解决方案一样? (22认同)
  • @Adam:我的理解是,因为setTimeout是非阻塞的,所以这不是重复 - 在每个setTimeout之后stackwindow关闭,并且只有一个setTimeout等待执行......对吗? (7认同)
  • 当迭代像`for in`循环这样的对象时,它会如何工作? (3认同)

cji*_*cji 69

尝试这样的事情:

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

  • 太棒了。超级简单,就像魅力。干得好! (2认同)

Sak*_*hta 61

如果使用ES6,您可以使用let以实现此目的:

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

为每次迭代let声明i了什么,而不是循环.这样,传递的内容正是我们想要的.setTimeout

  • 我相信这与 http://stackoverflow.com/a/3583795/1337392 中描述的答案具有相同的内存分配问题 (2认同)
  • @Flame_Phoenix 什么内存分配问题? (2认同)
  • setTimeout 调用在循环内同步计算 `i*3000` 参数的值,并将其按值传递给 `setTimeout`。`let` 的使用是可选的,与问答无关。 (2认同)
  • @Flame_Phoenix 提到这段代码存在问题。基本上,在第一遍中,您创建计时器,然后立即一次又一次重复循环,直到按条件(“i &lt; 10”)循环结束,因此您将有多个计时器并行工作,这会创建内存分配,并且在大量迭代时情况会更糟。 (2认同)

Jon*_*lms 41

由于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();
Run Code Online (Sandbox Code Playgroud)

关于MDN的参考

请注意,今天很少支持ES7,因此您需要与Babel进行交互,以便在任何地方使用它.

Transpiled

  • 很好的解决方案,但为了挑剔,我会调用函数“sleep”或“wait”而不是“timer”。类是名词,函数是动词。他们做某事或采取行动,而不是代表某件事。 (4认同)
  • 这对我来说是最好的解决方案,非常感谢! (2认同)
  • 这是迄今为止最好的解决方案,应该是公认的答案。接受的答案是hacky,不应该用于任何用途。 (2认同)

Fel*_*ing 21

另一种方法是将时间乘以超时,但请注意,这与睡眠不同.循环后的代码将立即执行,只延迟执行回调函数.

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

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

  • 值得指出的是,使用此方法无法在函数内可靠地使用`start`. (2认同)
  • 不良做法 - 不必要的内存分配. (2认同)
  • 为什么这是一个不好的做法,为什么它有内存分配问题?这个答案是否会遇到同样的问题?http://stackoverflow.com/a/36018502/1337392 (2认同)

小智 21

您可以创建一个sleep承诺的函数setTimeout。这使您能够使用async/await编写代码,而无需回调和熟悉的for循环控制流。

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

(async () => {
  for (let i = 0; i < 10; i++) {
    console.log(i);
    await sleep(1000);
  }

  console.log("done");
})();
Run Code Online (Sandbox Code Playgroud)

在 Node 中,您可以使用timers/promises以避免 Promisification 步骤(如果您的旧 Node 版本不支持该功能,则上述代码也同样有效):

const {setTimeout: sleep} = require("timers/promises");

// same code as above
Run Code Online (Sandbox Code Playgroud)

无论如何,由于 JS 是单线程的,所以超时是异步的是一件好事。如果不是,浏览器将没有机会重新绘制 UI,从而导致用户界面冻结。


BGe*_*sen 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

测试代码:

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();
Run Code Online (Sandbox Code Playgroud)

注意:使用警报会停止javascript执行,直到您关闭警报.它可能比您要求的代码更多,但这是一个强大的可重用解决方案.


Abe*_*efe 14

我可能会用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);
Run Code Online (Sandbox Code Playgroud)

  • 我稍微谷歌了一下,我什么都没发现,为什么setInterval不好?你能给我们一个链接吗?还是一个例子?谢谢 (14认同)
  • SetTimeout比settinterval好得多.谷歌,你会知道 (3认同)

小智 14

这会奏效

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

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


小智 13

在我看来,在循环中添加延迟的更简单、最优雅的方法是这样的:

names = ['John', 'Ana', 'Mary'];

names.forEach((name, i) => {
 setTimeout(() => {
  console.log(name);
 }, i * 1000);  // one sec interval
});
Run Code Online (Sandbox Code Playgroud)


Ita*_*zki 8

在ES6(ECMAScript 2015)中,您可以使用生成器和间隔进行延迟迭代.

生成器,ECMAScript 6的一个新功能,是可以暂停和恢复的功能.调用genFunc不会执行它.相反,它返回一个所谓的生成器对象,让我们控制genFunc的执行.genFunc()最初在其正文的开头被暂停.genObj.next()方法继续执行genFunc,直到下一个yield. (探索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;
  }
}
Run Code Online (Sandbox Code Playgroud)

所以如果你正在使用ES6,这是实现延迟循环的最优雅方式(对于我的看法).


Mar*_*o36 8

非常简单的单行解决方案,具有实际的异步等待延迟(没有排队的 setTimeout):

await new Promise((resolve) => {setTimeout(() => {resolve(true)}, 190)});
Run Code Online (Sandbox Code Playgroud)

异步函数内的实际延迟,作为常用的具有setTimeout不同超时间隔的多个 s 的替代方案,这可能会弄乱内存。对于此片段,延迟为 190 毫秒。

例子:

  • 在这 100 个循环中,每个循环中await都有一个new Promiseto resolve
  • 这种情况仅setTimeout在 190 毫秒后“允许”后才会发生。在此之前,代码会被 async-await /ending 阻塞Promise

await new Promise((resolve) => {setTimeout(() => {resolve(true)}, 190)});
Run Code Online (Sandbox Code Playgroud)


Dav*_*and 5

我用 BluebirdPromise.delay和递归来做到这一点。

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

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