直接上代码,想想下面的执行顺序
let fs = require('fs');setTimeout(function(){ Promise.resolve().then(()=>{ console.log('XXX2'); })},0);Promise.resolve().then(()=>{ console.log('XXX1');});fs.readFile('XXX',function(){ process.nextTick(function(){ console.log('nextTick') }) setImmediate(()=>{ console.log('setImmediate') });});复制代码
执行结果为
XXX1, XXX2, nextTick, setImmediate 复制代码
仔细想想为什么顺序是这样呢?
1: 首先我们要搞清楚Node.js 的Event Loop 到底是什么东西?
大家都知道JavaScript是单线程的,为什么是单线程的,因为要用 Javascript操作DOM,不可能对DOM操作加锁,这样使得JavaScript更加复杂,想想Java里面的并发包,就知道多线程环境并发操作是有多复杂了。
Node.js就是要让Javascript变为多线程的操作,所以就有了Event Loop,用来处理各种类型的回调函数,就是个执行非阻塞IO的一个操 作。既然都是多线程操作了,肯定就有线程调度和队列了。队列很 好理解,执行先进先出的回调函数的一个集合。
Node.js里有一个libuv,是一个高性能,事件驱动的IO跨平台的API 集合,是用C/C++写的,里面就是处理Event Loop的,调用操作系统的 API执行非阻塞IO操作。其实libuv底层还是有一些处理多线程所用到 的同步原语(互斥锁,读/写锁,信号量,内存屏障),还好我学过Java, 对这些都是非常了解的,尽管它是C++实现的。
2: 第二步,需要理解什么是microtask,什么是macrotask ?
为什么要有microtask和macrotask呢,是因为线程要调度嘛,把线程 调度的不同形态分成了两类,一类是microtask,一类是macrotask。 而在Node.js中,有很多是用来协调异步任务的,例如:setTimeout, setImmediate,process.nextTick,setInterval,Promise.resolve.t hen()。Node.js里规定microtask是比macrotask先执行的。 附上一张运行顺序图:
┌───────────────────────────────────┐┌─>│timers(计时器)执行 ││ |setTimeout以及setInterval的回调 | ----- 1│ └──────────┬────────────────────────┘│ ┌──────────┴────────────┐│ │ I/O callbacks ││ 处理网络,流,TCP的错误 │ ----------------2│ │ callback ││ └──────────┬────────────┘│ ┌──────────┴────────────┐│ │ idle, prepare ││ │ node内部使用 │-------------------3│ └──────────┬────────────┘ ┌───────────────┐│ ┌──────────┴────────────┐ │ incoming: ││ │poll(轮询) │<─────┤ connections, ││ │ 执行poll中的i/o队列检查 │ │data, etc. │-----4│ │定时器是否到时 │ └───────────────┘│ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ check ││ │ 存放setImmediate回调 │-------------------5│ └──────────┬────────────┘│ ┌──────────┴────────────┐└──┤ close callbacks │ │ 关闭的回调例如 │-------------------6 │ socket.on('close') │ └───────────────────────┘复制代码
在上面的图中,我们只需考虑1,4,5的时期,在最上面代码中,Promise.resolve().then()属于microTask,而setTimeout和setImmediate属于macroTask,显然,问题就迎刃而解了。