请问为什么Nodejs中会在for循环结束才会执行异步函数?

请问为什么Nodejs中会在for循环结束才会执行异步函数?

请看这段代码:

for(var i=0; i<10; i++){
  var now = new Date();
  while (new Date - now < 1000);
  console.log("here");
  setTimeout(function(){
    console.log(i);
  }, 5000);
}

setTimeout在这应该是异步函数吧!我使用了while来使每个循环延迟1s,按理当执行第一次循环执行了setTimeout,他会在后台静默执行,不会阻塞。但是console.log(i);都是在循环执行完成后才会输出。请问这是什么原因?谢谢!

另外我注意到循环结束后立刻输出了4个console.log(i);那应该是执行循环后setTimeout就已经在后台执行了? 输出如下:here here here here here here here here here here 10 10 10 10 10 10 10 10 10 10


8 回复

当然可以,让我们来分析一下你提供的代码,并解释为什么 setTimeout 的回调函数会在循环结束后才被执行。

问题分析

在你的代码中,你创建了一个 for 循环,每次循环都会延迟1秒(通过 while 循环实现),然后设置一个5秒的 setTimeout 来打印变量 i 的值。问题是,所有的 setTimeout 回调函数似乎都在循环结束后才被执行。

原因

主要原因在于 JavaScript 的事件循环机制以及闭包的作用域链。具体来说:

  1. 闭包和作用域链:在每次循环中,你创建了一个匿名函数并将其传递给 setTimeout。然而,这些匿名函数引用了外部循环中的变量 i。由于 JavaScript 的作用域链机制,这些匿名函数在被调用时会访问到循环结束后 i 的值。

  2. 事件循环:JavaScript 是单线程的,所有同步代码都会在一个队列中依次执行,而异步操作(如 setTimeout)会被放入事件队列中等待执行。只有当当前同步任务执行完毕后,事件队列中的异步任务才会被执行。

示例代码解析

for(var i=0; i<10; i++){
  var now = new Date();
  while (new Date() - now < 1000); // 延迟1秒
  console.log("here");
  setTimeout(function(){
    console.log(i);
  }, 5000);
}
  • 同步部分:每次循环中,首先创建了一个 now 变量来记录时间戳,然后通过 while 循环进行1秒的延迟。
  • 异步部分:在延迟之后,设置了一个5秒的 setTimeout 来打印变量 i 的值。

输出结果

你观察到的结果是:

here
here
here
here
here
here
here
here
here
here
10
10
10
10
10
10
10
10
10
10

这是因为每个 setTimeout 回调函数在循环结束后才开始执行,并且它们都访问到了循环结束后的 i 的值(即 i 的最终值为10)。

解决方案

为了避免这种问题,你可以使用闭包来捕获每次循环中的 i 的值,或者使用 let 而不是 var,因为 let 在每次循环中会重新声明变量,从而避免共享同一个变量的问题。

for(let i=0; i<10; i++){
  var now = new Date();
  while (new Date() - now < 1000); // 延迟1秒
  console.log("here");
  setTimeout(function(){
    console.log(i);
  }, 5000);
}

这样,每个 setTimeout 回调函数将正确地捕获每次循环中的 i 的值。


在for里面用自调函数就好了

我知道console.log(i); 输出的值是10,但是奇怪的是为什么要循环结束才会输出?而不是在执行循环的时候输出值?

settimeout是js中很重要一个原生函数,也是实现promise的核心。 javascript代码都是同步执行的,代码都在在一个代码“队列”里面。与此同时javascript还有一个“Event Queue”,事件队列里都是处理一些异步的callback/handler,处理ajax response,点击啊,文件,数据库操作结果。关键是,只有代码队列所有代码都执行完毕了,javascript才会从事件队列里取出一个callback/handler来执行。 在你的例子里面,settimeout就是把callback,放到了这个事件队列里面,直到当前的for loop执行完毕了,js才从事件对列里取出callback,才执行了callback函数,找到执行完毕那个状态下的closure 中i的值,并打印出来,所以即使你设置 settimeout(function(){}, 0); 也是同样的结果。

请问为什么会在for循环结束才会执行异步函数? 如果在for循环中执行异步函数,那就不叫异步了。

setTimeout 不是异步的,实际上它只是伪异步,造成异步的现状是它用了 插入时间点的机制来实现的,setTimeout的第二个参数指明了执行的时间。所以你写的代码执行结果是没问题的 , 如果楼主不信的话 可以把while写成一个死循环 这样的话 你的setTimeout一辈子都不会执行

for ( var i = 0; i < 5; i++ ) {  
    setTimeout(function() {  
        alert( i );  
    }, i * 100 );  
}

这是JQuery官网上在讲解闭包的时候的一个例子,这里的alert也只会输出5.

参考知乎上的一个解答

在Node.js中,setTimeout 确实是异步函数,但 for 循环中的变量 i 是共享的。由于 JavaScript 的作用域和闭包特性,所有定时器函数最终引用的是同一个 i 变量。当定时器函数在5秒后执行时,循环已经完成,i 的值已经变成了10。

你可以通过立即执行函数表达式(IIFE)或 let 声明来解决这个问题,let 声明会在每次循环迭代中创建一个新的作用域。

示例代码:

使用 IIFE

for (var i = 0; i < 10; i++) {
  (function(i) {
    var now = new Date();
    while (new Date() - now < 1000);
    console.log("here");
    setTimeout(function() {
      console.log(i);
    }, 5000);
  })(i);
}

使用 let

for (let i = 0; i < 10; i++) {
  var now = new Date();
  while (new Date() - now < 1000);
  console.log("here");
  setTimeout(function() {
    console.log(i);
  }, 5000);
}

这两种方法都能确保每个定时器函数内部的 i 是独立的,从而正确打印出 0 到 9 的数字。

回到顶部