【问题标题】:Use setTimeout inside while loop in Node.js在 Node.js 的 while 循环中使用 setTimeout
【发布时间】:2018-04-12 16:57:16
【问题描述】:

我想在 while 循环内的 setTimeout 中保持 nodejs 执行。我使用了异步瀑布函数,但它在 while 循环中不起作用。所以我使用了下面的代码:-

    var i = 3;

    while(i> 0)
    {
        setTimeout(function(){
            console.log('start', i);

            setTimeout(function(){ console.log('timeout');}, 1000);

            console.log('end', i);

            i--;
        }, 1000);

    }
console.log("execution ends");

但我没有得到预期的输出。 我的预期输出将是:-

    start3
    timeout
    end3
    start2
    timeout
    end2
    start1
    timeout
    end1
    execution ends

【问题讨论】:

  • 你知道setTimeout() 没有阻塞。它只是安排一些东西在未来运行,然后你的其余代码继续运行。这意味着您的 while 循环会一直运行设置计时器,并且永远不会给它们运行的​​机会。
  • 不要运行while循环!!

标签: javascript node.js async.js


【解决方案1】:

方式一:使用while loop

var i = 3
var p = Promise.resolve(i)
while (i > 0) {
  (i => {
    p = p.then(() => {
      return new Promise(function (resolve, reject) {
        console.log('start', i)
        setTimeout(function () {
          console.log('timeout')
          console.log('end', i)
          resolve()
        }, 1000)
      })
    })
  })(i)
  i--
}
p = p.then(data => console.log('execution ends'))

方式2:

function test(i) {
  console.log('start', i)
  setTimeout(() => {
    console.log('timeout')
    console.log('end', i)
    i--
    if (i < 0) {
      return
    }
    test(i)
  }, 1000)
}
test(3);

方式3:使用async

async function test (i) {
  console.log('start', i)
  await new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log('timeout')
      console.log('end', i)
      i--
      if (i < 0) {
        reject(i)
      }
      resolve(i)
    }, 1000)
  })
    .then(i => test(i))
    .catch(i => console.log('done', i))
}
test(3)

【讨论】:

  • 你认为await setTimeout() 是做什么的?提示,await 只有在你给它一个承诺时才会真正等待,而不是 timerId。
  • 这确实不是正确的编程方式。 asyncawait 在这里根本没有做任何有用的事情。事实上,您可以同时删除它们。递归解决方案是解决此问题的一种方法,但教这种使用 asyncawait 是错误的。
  • 仅使用while循环可以获得相同的输出吗?
  • @jfriend00,抱歉弄错了。现在更新了,第二个呢?
  • @KetanBodarya,第一个是你的选择,你不这么认为吗?
【解决方案2】:

nodejs 本质上是异步的,setTimeout 或多或少有点像在新线程上执行某些东西(或多或少是因为 JS 是单线程并使用事件循环)。

查看这个 npm 包: https://www.npmjs.com/package/sleep

这应该可以解决问题...
但显然你应该使用sleep 仅用于调试目的。 在生产代码中你最好拥抱 NodeJS 的异步特性并使用 Promises

查看更多详情: 事件循环:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

设置超时:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

承诺:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

【讨论】:

  • setTimeout() 根本不像在新线程上执行某些操作。它是非阻塞的。它只是安排一些东西在未来的某个时间运行。这就像在您的日历中添加提醒一样。你的余生继续,然后在未来的某个时间,提醒会触发。教一个 node.js 新手用sleep() 解决问题是一个非常非常糟糕的主意。必须学习正确的异步开发才能在 node.js 中编写成功的代码。
  • 我把它简化了……你看到“或多或少”了吗?并且 OP 要求睡眠...如果您将其用于调试目的,则没有任何问题...
  • 嗯,它根本不像“或多或少”。你建议使用sleep() 只是解决node.js 中任何异步问题的错误方法,除了一个罕见的调试问题。甚至 sleep NPM 页面都说它主要用于调试,因为它阻塞了事件循环。任何阻塞 node.js 中事件循环的代码对于任何类型的服务器开发来说都是非常糟糕的,因为它破坏了服务器的所有可伸缩性。这不是你应该教给新手的,因为它永远不应该在服务器设计中使用。
  • OP 正在询问如何在 node.js 中编写适当的代码来生成特定的结果。他们不是在问如何调试某些东西。 sleep() 是错误的建议。
  • 第一句话是“我想保持 nodejs 执行”......但无论如何我更新了答案以警告这一点并添加了关于 Promise 的注释
【解决方案3】:

您的程序与您的预期输出存在一些问题。第一个 i-- 仅在 1000 毫秒后起作用。到那时,将会运行太多的 while 循环。 setTimeOut 也是非阻塞的,因此,execution ends 将在第一个 start 被安慰之前得到安慰。为了达到您的预期结果,您可以对代码进行一些更改:

var i = 3;var j =3;

    while(j> 0)
    {
        setTimeout(function(){
            console.log('start', i);
            console.log('timeout');
            console.log('end', i);

            i--;
        }, 1000);
        j--;
    }
    if(j == 0){
        setTimeout(function(){
            console.log('execution ends');
        }, 1000);


    }

【讨论】:

    【解决方案4】:

    请尝试以下 sn-p。您可以在计时器方法中引入 API 调用或异步调用来代替 setTimeout。

    const timer = () => {
        return new Promise(res => {
            setTimeout(() => {
                console.log('timeout');
                res();
            }, 1000);
        });
    }
    
    let i = 3;
    let temp;
    
    while (i > 0) {
        if (temp !== i) {
            temp = i;
            console.log('start', temp);
            timer().then(() => {
                console.log('end', temp);
                i -= 1;
            });
        }
    }
    

    【讨论】:

      【解决方案5】:

      首先,您必须了解 Javascript 中的 setTimeout() 是非阻塞的。这意味着它所做的只是安排稍后运行,然后您的其余代码立即继续运行。

      在您的特定代码示例中,您将有一个无限循环,因为 while 循环会一直持续下去,继续调度越来越多的计时器,但在 while 循环停止之前,这些计时器都无法运行。而且,在其中一个计时器可以运行之前,您的循环变量 i 永远不会更改,因此 while 循环永远不会停止。

      要了解它为何如此工作,您必须真正了解 Javascript 和 node.js 的事件驱动设计。当您调用setTimeout() 时,它会在 JS 引擎内部安排一个内部计时器。当该计时器触发时,它会在 Javascript 事件队列中插入一个事件。下次 JS 解释器完成它正在做的事情时,它将检查事件队列并将下一个事件拉出队列并运行它。但是,您的 while 循环永远不会停止,因此它永远无法到达任何新事件,因此它永远无法运行您的任何计时器事件。

      我将向您展示生成所需输出的三种不同方法,它们都在可运行代码 sn-ps 中,因此您可以在答案中直接运行它们以查看它们的结果。

      第一种技术是在纯 Javascript 中完成的,只需在未来的不同时间设置计时器,以使各种所需的输出以正确的顺序触发:

      不同的计时器

      let cntr = 3;
      for (let i = 1; i <= 3; i++) {
          // schedule timers at different increasing delays
          setTimeout(function() {
              console.log('start', cntr);
              setTimeout(function() {
                  console.log('timeout');
                  console.log('end', cntr);
                  --cntr;
                  if (cntr === 0) {
                      console.log("execution ends");
                  }
              }, 1000);
          }, i * 2000);
      }

      或者,如果你真的希望它是一个 while 循环:

      let cntr = 3, i = 1;
      while (i <= 3) {
          // schedule timers at different increasing delays
          setTimeout(function() {
              console.log('start', cntr);
              setTimeout(function() {
                  console.log('timeout');
                  console.log('end', cntr);
                  --cntr;
                  if (cntr === 0) {
                      console.log("execution ends");
                  }
              }, 1000);
          }, i * 2000);
          i++;
      }

      使用 Promise 对操作进行排序

      function delay(t, v) {
          return new Promise(resolve => {
              setTimeout(resolve.bind(null, v), t);
          });
      }
      
      function run(i) {
          return delay(1000).then(() => {
              console.log("start", i);
              return delay(1000).then(() => {
                  console.log("timeout");
                  console.log("end", i);
                  return i - 1;
              });
          });
      }
      
      run(3).then(run).then(run).then(() => {
          console.log("execution ends");
      });

      如果您愿意,也可以将其放入循环中:

      function delay(t, v) {
         return new Promise(resolve => {
             setTimeout(resolve.bind(null, v), t);
         });
      }
      
      function run(i) {
          return delay(1000).then(() => {
            console.log("start", i);
            return delay(1000).then(() => {
                console.log("timeout");
                console.log("end", i);
                return i - 1;
            });
          });
      }
      
      [3, 2, 1].reduce((p, v) => {
           return p.then(() => {
               return run(v);
           });
      }, Promise.resolve()).then(() => {
          console.log("execution ends");
      });

      Promises Plus ES7 Async/Await

      此方法使用 ES7 的 await 特性允许您使用 Promise 和 await 编写类似顺序的代码,这可以使此类循环变得更加简单。 await 在等待 Promise 解决时阻止函数的内部执行。它不会阻塞函数的外部返回。该函数仍然立即返回。它返回一个承诺,当内部函数的所有阻塞部分都完成时,该承诺会解决。这就是为什么我们在runSequence() 的结果上使用.then() 来知道什么时候完成。

      function delay(t, v) {
         return new Promise(resolve => {
             setTimeout(resolve.bind(null, v), t);
         });
      }
      
      async function runSequence(num) {
          for (let i = num; i > 0; i--) {
              await delay(1000);
              console.log("start", i);
              await delay(1000);
              console.log("timeout");
              console.log("end", i);
          }
      }
      
      runSequence(3).then(() => {
          console.log("execution ends");
      });

      这个await 示例说明了promise 和await 如何简化ES7 中的操作顺序。但是,它们仍然要求您了解 Javascript 中的异步操作是如何工作的,因此请不要尝试先跳过该级别的理解。

      【讨论】:

        【解决方案6】:

        你没有得到预期的输出,因为你的代码中有闭包,像这样改进你的代码:

        var i = 3;
                    while(i> 0)
                    {
                        setTimeout((
                            function(i){
                                return function(){
                                    console.log('start', i);
                                    setTimeout(function(){ console.log('timeout');}, 1000);
                                    console.log('end', i);
                                    i--;
                                }
                            }
                        )(i), 1000);
                    }
        

        【讨论】:

        • 您自己尝试过吗?它经过while 循环多少次?
        • 你要输出i的值吗?
        • 在这段代码中,调试器卡在第一个 setTimeout 的 while 循环之间。
        • OP 显示了他们想要的预期输出。你真的运行你的代码。您的代码是否生成预期的输出?提示,这是一个无限的while 循环,没有计时器有机会触发。这是错误的。你应该尝试自己运行它(我做过)。
        • @zero 我想要与我在使用 while 循环时提到的完全相同的输出。
        猜你喜欢
        • 2012-10-11
        • 2015-11-30
        • 2013-03-18
        • 2016-07-01
        • 2012-07-18
        • 2017-11-13
        • 2019-04-18
        • 1970-01-01
        相关资源
        最近更新 更多