首先看下这段代码的输出是什么:

1
2
3
4
5
6
7
8
9
10
11
setTimeout( () => {
console.log( 1 );
}, 1 );

setImmediate( () => {
console.log( 2 );
} );

process.nextTick( () => {
console.log( 3 );
} );

我猜大概大部分人的电脑上的输出结果是:3 1 2,并且无论你执行多少次,结果永远是3 1 2

再看看下一段代码的执行结果:

1
2
3
4
5
6
7
setTimeout( () => {
console.log( 1 );
}, 1 );

setImmediate( () => {
console.log( 2 );
} );

其实就是把process.nextTick调用去掉了,这时候你会发现输出开始变得较为随机:1 2或者2 1

那么第一段代码哪些人的电脑输出结果会不一样?土豪!没错就是土豪,土豪不仅仅在生活上碾压你,在代码上甚至都是如此。

NodeJs的EventLoop

有关NodeJs的EventLoop不多说了,网上讲解文章非常多,官网就有一篇讲得非常好也通俗易懂的文章:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

看完上面的文章也许你对第二段代码的随机输出明白了,但是第一段为何始终都是3 1 2呢?

为什么是3 1 2?

也许你会觉得结果应该会是在3 2 13 1 2之间随机,但是你可以在你自己的电脑上面试试看,基本永远都是3 1 2,那么为什么呢?

上面的文章只介绍了事件循环相关的内容,但是对于nextTick以及整个NodeJs执行流程的介绍并不多,这里自己通过查看源码发现以下流程:

  1. NodeJs启动阶段

    • 准备Node环境
    • 执行代码
    • 检查nextTickQueue,如果有就执行。
  2. 开始事件循环,这一步一定是在启动阶段之后,因为事件循环的前提是得有事件触发,事件触发的前提就是执行了我们的代码,如果没有触发事件,那么Node进程就退出了。

    • timer
    • I/O

看到这里,就已经能非常好地解释上面的代码了:

  1. 首先我们的主程序执行完,nextTickQueue中有一个回调,首先执行这个回调,打印一个console.log

  2. 然后开始事件循环,执行TimerI/O等阶段,然而因为我们设置的timer的超时时间是1毫秒,而我们在nextTick里面的回调的console.log还是比较耗时间的,到了Timer阶段,1毫秒早就过去了,所以执行结果永远都是3 1 2

其实我测试了一下,我的电脑设置为setTimeout( ..., 4 ),4毫秒的延迟后,就会开始出现随机结果,也就是一次console.log差不多花费3毫秒的时间,相当耗时啊,并且在process.nextTick回调里面把console.log去掉后,开始出现随机1 2或者2 1的结果。

为什么土豪的执行结果可能不是3 1 2呢?土豪的电脑配置高,一个console.log也许花不了几微秒,Timer阶段检查1毫秒没到,就往下执行了,之后就到了check阶段,执行setImmediate的回调,输出就变成了3 2 1

所以,努力奋斗吧,争取买得起一台输出结果是3 2 1的电脑,哈哈。