Node.js的异步模型
核心:Node.js是单线程的,但它底层借助libuv库实现的事件循环,实现了高效的非阻塞I/O;
- 单线程:是指执行Javascript的代码的主线程是单线程的,这避免了多线程中令人头痛的资源竞争和状态同步问题
- 非阻塞I/O:当你发起一个读取文件或者网络请求的异步操作时,主线程不会傻等,而是继续执行后续代码。当操作完成时,对应的回调函数会被放置到任务队列中等待执行。
- 事件循环:它是一个while(true)死循环,负责不断地检查各个任务队列,并从中取出回调函数放到调用栈中执行。它是连接异步操作结果和Javascript回调的桥梁
事件循环的6大阶段
Node.js的事件循环分为多个阶段,每个阶段都有自己的任务队列。
- timers阶段(定时器阶段):执行setTimeout,setInterval中到期的回调;延迟时间并不是精确的,它只是表示回调被尽早执行的毫秒数。如果时间循环正阻塞在poll阶段,定时器回调可能会被延迟执行。
- Pending Callback阶段
- Idle,prepare阶段,Node.js内部阶段
- Poll(轮询阶段):
- 获取新的I/O事件:执行几乎所有的I/O回调(除定时器回调,关闭回调,setImmediate())
- 处理轮询队列:如果poll队列不为空,事件循环会同步执行队列里的回调,直到队列被清空或达到系统上限。
- 等待事件:如果po队列为空,他会检查是否有setImmediate()任务
- 如果有,则结束poll阶段,进入check阶段
- 如果没有,则阻塞在此阶段,等待新的I/O事件到来,然后等待新的I/O事件的到来,然后立即执行其回调。
- check(检查阶段):专门执行setImmediate的回调,setImmediate是一个特殊的异步API,他的回调会在poll阶段结束后立即执行
- close callbacks(关闭回调阶段):执行一些关闭事件的回调,例如socket.on('close',...)
任务优先级:微任务和宏任务
除了上述阶段中的任务(可统称为宏任务,如setTimeout,setImmediate,I/O),Node.js中还有微任务。微任务在每个阶段完成后都会被执行,优先级非常高
- 宏任务:setTimeout,setInterval,setImmediate,I/O操作
- 微任务
- process.nextTick():这是Node.js中优先级最高的微任务,它会在当前操作完成后,立即执行,甚至比Promise的回调还要早
- Promise的回调:优先级仅次于process.nextTick
核心执行规则:
在一个阶段执行完毕之后,事件循环并不会直接进入下一个阶段,而是会先去清空所有的微任务队列(先清空所有的process.nextTick(),再清空所有的Promise),之后才会进入下一个阶段。