请问为什么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
当然可以,让我们来分析一下你提供的代码,并解释为什么 setTimeout
的回调函数会在循环结束后才被执行。
问题分析
在你的代码中,你创建了一个 for
循环,每次循环都会延迟1秒(通过 while
循环实现),然后设置一个5秒的 setTimeout
来打印变量 i
的值。问题是,所有的 setTimeout
回调函数似乎都在循环结束后才被执行。
原因
主要原因在于 JavaScript 的事件循环机制以及闭包的作用域链。具体来说:
-
闭包和作用域链:在每次循环中,你创建了一个匿名函数并将其传递给
setTimeout
。然而,这些匿名函数引用了外部循环中的变量i
。由于 JavaScript 的作用域链机制,这些匿名函数在被调用时会访问到循环结束后i
的值。 -
事件循环: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 的数字。