前言
最近在学习 HTTP 和 Node.js 相关的一些东西,越发感觉对以往没搞明白的一些问题自然而然的懂了(比如浏览器缓存方面)。因此决定先把前后端的开发的相关领域给打通,对每个环节都有一个较为全面的了解。这应该会对自己对于整个项目开发流程的理解会有一个较大的提升。
注:此篇文章主要是:
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
的体会加上了自己的一些体会。但由于这篇文章是纯英文的,后面的部分还是没有很好地理解。往后需要根据自己对这些东西的理解与后面重复看这篇文章的体会来进行补充。
一、事件循环
我以前也总结过关于JavaScript的事件循环机制:
同样的在Node.js中Event Loop也是其执行机制的重要的一环,Event Loop的职责就在于不断的等待事件的发生,然后将这个事件的所有处理器一他们订阅这个事件的时间顺序依次执行,当这个事件的所有处理器都被执行完毕后,事件循环就会开始继续等待下一个事件的出发,不断往复,直到所有事件全部执行完毕。
虽然这样说起来 Event Loop并不复杂,但在Node.js中,Event Loop存在着属于自己的规则。我们首先来看下面的这段代码:
1 | const { readFile } = require('fs') |
如果对Node.js事件循环机制熟悉的大佬,应该仔细看看就知道它的打印结果了,而像我这样的咸鱼在没有好好了解Node.js事件循环机制的人来说,将打印结果事先确定就有点难度了,但没关系,都是需要学习的嘛!要搞清楚这个问题,首先我们要清楚的是,虽然Node.js采用V8引擎作为JavaScript的执行引擎,但同时也使用了libuv来实现自己的事件驱动式的异步I/O。因此要搞清楚Node.js的时间循环机制,最好的方式就是去看libuv的源码了。不过libuv的源码是C++的,所以在网上有搜了下,挺快的就搜到了关于这段代码的注释:
1 | //deps/uv/src/unix/core.c |
然后我们就可以再根据这张广为流传的Node.js的流程图一起学习:
官方的还有个这种流程图:
1 | ┌───────────────────────────┐ |
总结起来主要有以下几个阶段:
- 定时器:这一阶段会执行由 setTimeout() 和 setInterval() 所设置的回调。
- I/O 回调:执行除了关闭回调、定时器的回调和 setImmediat() 以外的几乎所有的回调。
- ide,prepare:仅内部使用。
- 轮询:检索新的I / O事件;执行与I/O相关的回调(几乎所有回调都是关闭回调,定时器和setImmediate()调度的回调);node 将在适当的时候阻塞在这里。
- check: setImmediate() 的回调。
- 关闭事件回调:比如 socket.on(‘close’, …) 的回调。
注:每一格称为事件循环的一个阶段。
值的注意的是,以上的每一阶段都有一个先进先出的待执行任务队列。而且在每一阶段,其内部都有自己的执行方法,当进入其中一个阶段时,每个阶段都会执行任何该阶段自己特定的操作,然后才执行在该阶段的队列中的回调,直到队列里的回调都执行完了或执行的次数达到最大限制。当队列耗尽或执行的次数达到最大限制时,事件循环进入下一个阶段,如此循环。
因此按照上面的思路,我们逐个分析可以得到,上面的那串代码的执行结果如下:1
2
3
4
5
6
7
8
9
10
11
12这里是global
process.nextTick 执行了
call 执行拉!
第一个Promise then
第二个Promise then
这是Promise里的process.nextTick
0 毫秒后执行此回调
immediate执行了
读取了package文件
读取了DOM文件
100 毫秒后执行此回调
200 毫秒后执行此回调