【问题标题】:Nested setTimeout() calls don't run in the correct order in javascript?嵌套的 setTimeout() 调用在 javascript 中没有以正确的顺序运行?
【发布时间】:2019-03-20 04:18:10
【问题描述】:

我正在尝试迭代一个数组,然后在每次迭代中执行 2 个步骤(我们称之为第 1 步和第 2 步)。每次迭代之间以及步骤 1 和步骤 2 之间应该有一个延迟。为了添加延迟,我使用 setTimeout() 方法。

基本上,类似于 -

对于i从0到array.length

执行步骤 1

等待 2-5 秒

执行第 2 步并增加 i

等待 5-9 秒,然后继续循环

下面是我的代码(来自相关问题-How to run setTimeout() with a random interval in each iteration in javascript?)-

function displayValue(){
   var l = ['a' , 'b', 'c'];
   var delay = 17000;
   var i = 0;
   function timerFunction(i){
      if(i === l.length)
          return;
      setTimeout(()=>{
         console.log("Step 1 - Iteration  - " + i + " - " + l[i] + "  -  " + new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds());
         setTimeout(() => {
           console.log("Step 2 - Iteration - " + i + " - " + l[i] + "  -  " + new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds());
           //i++ should probably be here but then i is never incremented
        }, 2000 + Math.floor(Math.random() * 3000));
     i++;
     timerFunction(i);
   }, delay);
   delay = 5000 + Math.floor(Math.random() * 3500);
   } 
   timerFunction(i);
 }

 displayValue();

当我运行上述代码时,第 2 步在第 1 步之前打印,用于第一次之后的所有迭代,并且循环运行超过 array.length。输出类似于 -

第 1 步 - 迭代 - 0 - a - 9:17:14

第 2 步 - 迭代 - 1 - b - 9:17:18

第 1 步 - 迭代 - 1 - b - 9:17:21

第 2 步 - 迭代 - 2 - c - 9:17:24

第 1 步 - 迭代 - 2 - c - 9:17:28

第 2 步 - 迭代 - 3 - 未定义 - 9:17:30

我怀疑这是因为,我需要在内部 setTimeout() 中增加 i,但是当我将 i++; 移动到那里时,它可能会完全停止增加,因为它成为该方法中的局部变量。有没有办法在内部 setTimeout() 中通过引用传递 i?或者其他一些解决方案,以防我完全关闭?

【问题讨论】:

  • 您是否尝试过将这些变量置于函数全局范围之外?
  • 您的预期输出是什么?很难理解你想做什么。对于您的情况,您可能首先计算所有步骤的延迟时间,然后同时在同一级别调度所有 setTimeout。
  • 您是否考虑过研究 javascript 迭代器?似乎它们会VASTLY 简化代码的实现,因为您只需请求集合中的下一项。您可以循环执行,退出循环,执行其他操作,请求下一个项目并得到您想要的。这似乎很有帮助,因为您可以避免所有可怕的嵌套间隔代码。更多信息:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 与此同时...您为自己创建的问题确实是一种竞争条件。为了在本质上都是异步的两位代码中使用相同的变量(在这种情况下为i),您必须将内部函数包装在称为闭包的结构中。最简单的方法是使用IIFE
  • @enhzflep 我看不到迭代器如何帮助“大大简化 [ing]”这段代码。他们仍然需要两个嵌套的 setTimeouts,迭代数组并不是这里的难点。你能分享一个例子吗?我想我错过了什么。

标签: javascript


【解决方案1】:

您的问题是您正在从第一个 setTimeout 回调继续循环,而此时,第二个仍未触发。

因此,您只需要从二级超时内递归调用timerFunction

function displayValue() {
  var l = ['a', 'b', 'c'];
  var delay = 1000;
  timerFunction(0);

  function timerFunction(i) {
    if (i === l.length)
      return;

    setTimeout(() => { // level 1
      console.log("Step 1 - Iteration  - " + i + " - " + l[i] + "  -  " + new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds());
      // we start level2 timeout
      setTimeout(() => {
        console.log("Step 2 - Iteration - " + i + " - " + l[i] + "  -  " + new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds());
        // only when this one is done, we start again
        timerFunction(++i);
      }, 500 + Math.floor(Math.random() * 500));
    }, delay);
    delay = 1000 + Math.floor(Math.random() * 800);
  }
}

displayValue();

但请注意,这与您对应该发生的情况的描述不符。这里是:

function displayValue() {
  var l = ['a', 'b', 'c'];
  timerFunction(0);

  function timerFunction(i) {
    if (i === l.length) {
      return;
    }
    // Do Step 1
    level1();

    function level1() {
      console.log("Step 1 - Iteration  - " + i + " - " + l[i] + "  -  " + new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds());
      // Wait for 2-5seconds (here /10)
      setTimeout(level2, 200 + Math.floor(Math.random() * 300));
    }

    function level2() {
      // Do Step2
      console.log("Step 2 - Iteration - " + i + " - " + l[i] + "  -  " + new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds());
      // and increment i
      i++;
      // wait for 5-9 seconds and continue the loop
      setTimeout(() => timerFunction(i), 500 + Math.round(Math.random() * 400));
    }

  }
}
displayValue();

如果你可以使用 async/await 语法,那么你可以将它重写得更简洁:

displayValue();

function displayValue() {
  const l = ['a', 'b', 'c'];
  return iterate(0);

  async function iterate(i) {
    if(i >= l.length) return;
    step(1, i);
    await wait(200, 500);
    step(2, i);
    await wait(500, 900);
    return iterate(++i);
  }
  function step(type, index) {
    var d = new Date();
    console.log("Step " + type +
      " - Iteration  - " + index +
      " - " + l[index] +
      " -  " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds()
    );  
  }
}
function wait(min, max=min) {
  const delay = min + Math.floor(Math.random() * (max - min));
  return new Promise(res =>
    setTimeout(res, delay)
  );
}

【讨论】:

  • 非常感谢您的精彩回答!非常感谢您提供更简洁的代码和 async/await 变体。我不知道 javascript 中存在 async/await,代码变得更容易理解。我希望我能投不止一个赞成票!
猜你喜欢
  • 1970-01-01
  • 2014-11-15
  • 1970-01-01
  • 2017-02-21
  • 2015-10-02
  • 2021-08-11
  • 2011-06-19
  • 1970-01-01
  • 2016-12-26
相关资源
最近更新 更多