最近在学习node 方面的知识,碰到一个题目,挺有意义,在此记录一下。这也是在掘金的第一篇文章。
console.time('start');
setTimeout(function() {
console.log(2);
}, 10);
setImmediate(function() {
console.log(1);
});
new Promise(function(resolve) {
console.log(3);
resolve();
console.log(4);
}).then(function() {
console.log(5);
console.timeEnd('start')
});
console.log(6);
process.nextTick(function() {
console.log(7);
});
console.log(8);
下面我们带着问题去思考,关于setTimeout、setImmediate、process.nextTick、promise.then 的执行顺序。那接下来我们就一起分析一下这道题背后的原理和知识点。
在分析这道题之前,首先要搞清楚 setImmediate、process.nextTick 是干什么的。这里就涉及到了 node.js 的相关知识。
node 是什么
node是javascript运行环境,就像浏览器一样,是一个平台。在浏览器中,V8引擎负责解释javascript,你在javascript调用的接口都是浏览器实现并提供的,浏览器会调用底层的、由其他语言(C++)实现并封装好的接口来完成任务;同样,在node中,也是V8引擎负责解释javascript。详细了解请前往》
setImmediate、process.nextTick 是什么
为了协调异步任务,Node 提供了四个定时器,让任务可以在指定的时间运行
- setTimeout()
- setInterval()
- setImmediate()
- process.nextTick()
前两个是语言的标准,后两个是 Node 独有的 详细了解请前往》
node.js 核心特性
- 单线程
- 非阻塞I/O
- 事件驱动
- 浏览器端把鼠标点击、键盘按键等定义为事件,而node把网络请求、I/O操作等也看作事件,严格来说,一切动作都是事件,这就是事件驱动的思想
- 在程序启动时,便进入事件循环,不断遍历执行事件队列中产生的事件,而在执行过程中,又会产生新的事件,因此称为事件循环
题目解析
我们上面的题目,考察的就是 node 事件循环 Event Loop 我们可以简单理解Event Loop如下:
- 所有任务都在主线程上执行,形成一个执行栈(Execution Context Stack)
- 在主线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
- 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
- 主线程不断重复上面的第三步
在上述的例子中,我们明白首先执行主线程中的同步任务,因此依次输出3、4、6、8。当主线程任务执行完毕后,再从Event Loop中读取任务。
Event Loop读取任务的先后顺序,取决于任务队列(Job queue)中对于不同任务读取规则的限定。
Job queue中的执行顺序
在Job queue中的队列分为两种类型:
宏任务 Macrotask
宏任务是指Event Loop在每个阶段执行的任务
微任务 Microtask
微任务是指Event Loop在每个阶段之间执行的任务
我们举例来看执行顺序的规定,我们假设
宏任务队列包含任务: A1, A2 , A3
微任务队列包含任务: B1, B2 , B3
执行顺序为,首先执行宏任务队列开头的任务,也就是 A1 任务,执行完毕后,在执行微任务队列里的所有任务,也就是依次执行B1, B2 , B3,执行完后清空微任务队中的任务,接着执行宏任务中的第二个任务A2,依次循环。
了解完了宏任务 Macrotask和微任务 Microtask两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以node V8引擎为例),在node V8中,这两种类型的真实任务顺序如下所示:
宏任务 Macrotask队列真实包含任务:
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
微任务 Microtask队列真实包含任务:
process.nextTick, Promises, Object.observe, MutationObserver
由此我们得到的执行顺序应该为:
script(主程序代码)—>process.nextTick—>Promises...——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering
在ES6中宏任务 Macrotask队列又称为ScriptJobs,而微任务 Microtask又称PromiseJobs
我们的题目相对复杂,但是要注意,我们在定义promise的时候,promise构造部分是同步执行的
接下来我们分析我们的题目,首先分析Job queue的执行顺序:
script(主程序代码)——>process.nextTick——>promise——>setTimeout——>setImmediate
- 主体部分: 定义promise的构造部分是同步的,因此先输出3、4 ,主体部分再输出6、8(同步情况下,就是严格按照定义的先后顺序)
- process.nextTick: 输出7
- promise: 这里的promise部分,严格的说其实是promise.then部分,输出的是5、以及 timeEnd('start')
- setImmediate:输出1,依据上面优先级,应该先setTimeout,但是注意,setTimeout 设置 10ms 延时
- setTimeout : 输出2
综合的执行顺序就是: 3——>4——>6——>8——>7——>5——>start: 7.009ms——>1——>2
总结
此次主要从题目入手,理解setTimeout、setImmediate、process.nextTick、promise.then 的区别。关于node 方面的知识没有详细说明(正在学习中...)后续准备将自己的学习的知识进行总结分享,加深印象。