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

501

我想在一个while循环内添加延迟/休眠:

我尝试像这样实现:

alert('hi');

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

只有第一种情况是正确的:显示 alert('hi') 后,它将等待3秒钟,然后显示 alert('hello'),但随后 alert('hello') 将不断地重复显示。

我希望的是,在显示 alert('hi') 之后3秒钟后显示 alert('hello'),然后再等待3秒钟显示第二次 alert('hello'),以此类推。


for(var i=0; i < 5; i++){ delayLoop(i) }; function delayLoop(i){ setTimeout(function(){ console.log('每隔1秒打印一次', (i*1000)) } } - mhndlsz
const setTimeOutFn = async() => { for (var start = 0; start < 3; start++) { await new Promise(async(res, rej) => { setTimeout(() => { console.log('你好', start); res() }, 3000); }) } } - Bilal Khursheed
在每个循环中设置不同值的超时可能不是一个好主意。以下是一种使用Promise(async / await)实际上通过无限期挂起代码执行的无聊单行程序:https://dev59.com/DHA65IYBdhLWcg3w4C0L#73588338 - marko-36
33个回答

10

在ES6(ECMAScript 2015)中,您可以使用生成器和间隔迭代。

生成器是ECMAScript 6的新功能,它们是可以暂停和恢复的函数。调用genFunc不会执行它。相反,它返回一个所谓的生成器对象,让我们控制genFunc的执行。genFunc()最初在其主体开始时被暂停。方法genObj.next()继续执行genFunc,直到下一个yield。 (探索ES6)


Code example:

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,则这是实现带延迟的循环的最优雅方法(我个人认为)。


7

简单易懂的一行解决方案,包含实际的异步等待延迟(无需排队的 setTimeout):

await new Promise((resolve) => {setTimeout(() => {resolve(true)}, 190)});

作为常用的使用不同超时间隔的多个 setTimeout 可能会混乱内存的替代方法,这是一个异步函数中的实际延迟。在此片段中,延迟为190毫秒。

示例:

  • 在每个循环的100个迭代中,它await一个new Promise以进行resolve
  • 只有在经过190毫秒的setTimeout后,它才允许发生。在此之前,代码被异步等待/挂起的Promise阻塞。

async function delayTest() {
  for (let i=0; i<100; i++) {
    await new Promise((resolve) => {setTimeout(() => {document.write(`${i} `); resolve(true)}, 190)});
  }
};

delayTest()


5
我会使用 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>


这个使用本地的setTimeout而不是bluebeard可以正常工作,避免了依赖。我不会在这里使用i -= 1。如果添加更多使用调用中的i的逻辑,例如,用于索引数组,则可能具有意外值。此外,它实际上并不是递归的;子调用发生之前,调用堆栈就已经清除了。它只是恰好是相同的函数。您可以通过0的超时/延迟和几百万的i来证明这一点。 - ggorlen

4
在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 关键字一样定义全局变量或者整个函数的局部变量,无论块作用域如何。

4

无需函数的解决方案

虽然我有点晚到这个派对,但是有一种无需使用任何函数的解决方案:

alert('hi');

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

那将会安排10个警报以3秒间隔,而不是在alert()清除后等待3秒。如果第一个alert()在30秒内未被清除,则其余的警报将没有暂停时间。 - niry
这个确切的解决方案已经在2010年(由Felix Kling)提供过了。 - vsync

3

我想在这里也发表一下我的意见。这个函数运行一个带有延迟的迭代循环。请参考这个 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);
}

3
据我所知,setTimeout函数是异步调用的。您可以将整个循环包装在一个异步函数中,并等待包含setTimeoutPromise,如下所示:
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!")
});

请花些时间了解异步编程的基本概念。

3
除了10年前的已接受答案外,使用更现代的JavaScript可以使用async/await/Promise()或生成器函数来实现正确的行为。(其他答案中建议的错误行为将设置一系列3秒警报,而不管是否"接受"alert() - 或完成手头的任务)

Using async/await/Promise():

alert('hi');

(async () => {
  for(let start = 1; start < 10; start++) {
    await new Promise(resolve => setTimeout(() => {
      alert('hello');
      resolve();
    }, 3000));
  }
})();

使用生成器函数:

alert('hi');

let func;

(func = (function*() {
  for(let start = 1; start < 10; start++) {
    yield setTimeout(() => {
      alert('hello');
      func.next();
    }, 3000);
  }
})()).next();


2
你可以使用 RxJS interval 操作符interval 每隔 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>


1

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

这是 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接