在 Node.js 的 while 循环中使用 setTimeout

Use setTimeout inside while loop in Node.js

我想在 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

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

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

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

方式1:usewhile 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);

方式三:使用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)

您的程序 w.r.t 对于您的预期输出存在一些问题。第一个 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);


    }

请尝试以下代码段。您可以在计时器方法中引入 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;
        });
    }
}

首先,您必须了解 Javascript 中的 setTimeout() 是非阻塞的。这意味着它所做的只是稍后安排一些事情到 运行,然后你的代码的其余部分立即保持在 运行ning。

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

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

我将向您展示三种不同的方法来生成您想要的输出,它们都在 运行 可用的代码片段中,因此您可以 运行 在答案中直接查看它们的结果。

第一种技术是简单地 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 功能,允许您使用 promises 和 await 编写类似顺序的代码,这可以使此类循环变得更加简单。 await 在等待承诺解决时阻止函数的内部执行。它不会阻止函数的外部return。该函数仍然立即 returns。它 return 是一个承诺,当内部函数的所有阻塞部分都完成时就会解决。这就是为什么我们在 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 示例说明了 promises 和 await 如何简化 ES7 中的操作顺序。但是,它们仍然要求您了解异步操作在 Javascript 中的工作原理,因此请不要尝试先跳过该理解级别。