说明一下,我并非不知道正常的用法,也知道这两种添加异步代码的方式是不同的(很多人提到的本轮event loop 和下一轮的问题)。
我希望了解到的是为什么运行结果存在不同的可能,因为延时都是0 ms(按照规范也就是默认的浏览器内置的最小间隔 k ms)。
至于截图,已补。
代码如下:
let p = new Promise((res) => {
setTimeout(() => {
res(233)
}, 0)
})
let another = p.then(() => {
let a = 'then 1'
setTimeout(() => {
console.log(a)
}, 0)
}).then(() => {
console.log('then 2')
})
setTimeout(() => {
console.log('time')
}, 0)
// time
// then 2
// then 1
// 或者
// then 2
// time
// then 1
运行环境:node.js v6.11.2
系统:win10 专业版
其实直接拿setTimeout0做测试是不严谨的,0的时间是不能确定的,js代码每次执行花费的时间也是不确定的。有setTimeout0的时候,我一般会在这一层的最后加一个
确保在同步代码执行完毕后,setTimeout确实被触发了。
顺便纠正一个别的答案的关于then的连续调用的问题。
then
中没有返回值时,会自动创建一个promise,并且终态为调用then的promise的终态,也就是.then().then().then().....
可以一直这么写下去。具体分析
当前分析仅针对当前正式版本node 8.x,较低版本和未来版本不做保证。
先假设你已经了解了,node中事件循环的几个阶段,如果不了解的话可以看看浏览器和Node不同的事件循环(Event Loop),我们以这几个阶段为基础说下分析。
摘要:
我们先删去引起问题的timeout,看一下其他的流程
以确确实实开始执行的事件循环作为列举出的循环(还会有别的没有执行但是在等待的循环,会因为第一个timer的到来儿结束,进入下轮循环)
第一轮循环
timeout-promise
此时已经触发,已被加入Timers Queue,执行输出timeout-promise
,then1加入Microtask Queuetimeout-then
被创建,then2加入Microtask Queue,继续清空Microtask Queue,输出then.then
第二轮循环
timeout-then
已经触发,已被加入Timers Queue,在timer阶段,被执行,输出timeout-then
。所以输出是:
// timeout-promise// then.then <-第一轮// timeout-then <-第二轮
可以保证的是这三个顺序是确定的。
我们再来看一段代码
按照node的事件循环机制,我们依然以确确实实开始执行的事件循环作为列举出的循环。
由于setTimeout的0延迟实际肯定不为0,js代码每次执行花费的时间也是不确定的,这两种不确定性,导致了我们不知道setTimeout被添加到Timers Queue队列的具体时刻:
timeout-promise
的,那么这个时候Timers Queue里的后面有没有timeout-out
呢,可能前面的代码比较慢,已经触发,所以被加进来了?timeout-then
的,那么timeout-out
是在第一轮的队列里还是这这个的队列里的最前面呢,可能前面的代码比较快,导致第二轮循环之前才被触发,才被加进来?不管怎么样,我们可以确定的是,三个timeout的输出顺序是:
//timeout-promise <-第一轮// timeout-out <-第?轮// timeout-then <-第二轮
所以我才讲timeout之间的执行先后顺序是百分百确定的。
总结
结合上面的两种情况,有两个顺序是确定的:
// timeout-promise// then.then <-第一轮// timeout-then <-第二轮
//timeout-promise <-第一轮// timeout-out <-第?轮// timeout-then <-第二轮
把你的两种结果拆开来看,也是符合这个顺序的。
timeout-out
的位置的不确定才导致了出现了两种情况。所以我才讲需要加一个sleep函数,来确保,同步代码完成后,已经创建的两个
setTimeout0
都已经被触发,被加入到了第一轮循环的Timers Queue
中。其他示例
一个setTimeout和一个setImmediate
我们知道在同一轮循环中,setTimeout执行的阶段比setImmediate执行的阶段要靠前,但是这段代码
基本上有对半的几率是先
Immediate
,这就是因为setTimeout在第二轮循环才被加入了Timer Queue队列中,有两个解决办法:很多setTimeout和一个setImmediate
你可以看到这个
我是第一轮
可能会出现在任意位置。