本文主要介绍nextTick、timers API、MicroTasks几类任务是在什么时候注册和执行的,也会从node 的bootstrap到evnet-loop过程做一个简单的介绍
整体流程
在这里以下面这段代码为例子,画一下整体的运行流程:
setTimeout(() => console.log('timers API'), 10)
new Promise((resolve, reject) => resolve('microtask run')).then(arg => console.log(arg))
process.nextTick(() => console.log('run next tick'))
setImmediate(() => console.log('setImmediate API'))
注意:
- timers每个阶段都会执行
InternalCallback
以及InternalCallback::Close
,在这里为了简化,只画了timers的流程 - 图中标红的地方是和
nextTick
以及microTask
相关的流程 - 图中只提供与本文相关的流程,不是node从运行到结束的全部流程
- 这里只是简化的流程,把nextTick、timers API、MicroTasks串起来了。如果想了解每个知识点详细的流程,请移步:
node源码粗读(6):从./lib/timers.js来看timers相关API底层实现
node源码粗读(7):nextTick和microtasks从bootstrap到event-loop全阶段解读
node源码粗读(8):setImmediate注册+触发全流程解析
Environment::Start
在这里不做过多详细介绍了,之前的文章中介绍过很多。有一个知识点需要注意就是:Immediate是在这个阶段注册到uv_check_start
中的。此Immediate其实是Environment::CheckImmediate
函数,保证之后event-loop在运行的时候能在check阶段运行这个函数,进而触发immediate_callback_function
实现对ImmediateList的调用。
至于idle部分,是为了在有ImmediateList的时候直接跳过poll阶段,毕竟poll是阻塞运行的。
bootstrap阶段
bootstrap阶段是node运行时候的构建阶段,也是最基础的阶段。bootstrap阶段会把整体的node架子搭起来(在这里不做详细介绍),之后运行业务代码。比如本文中的这个例子,上图中画的应该也比较清晰了,顺序执行。唯一的区别是:在执行不同API的时候,callback的去向是不一致的。从上图中也能看出来,这几个API最终的去向分别是:TimersList、microTask、immediateQueue、nextTickQueue。
其中,setTimeout在注册的时候会创建TimerWrap的实例,在创建实例的时候会初始化uv_timer,之后再通过TimerWrap.start启动uv_timer_start,开始监听,到时间触发运行回调函数:
nextTick在注册之后且bootstrap构建结束后运行SetupNextTick
函数,从而触发nextTick的运行,而nextTick在运行之后会触发runMicroTasks()
,清空bootstrap阶段的microTask:
event-loop阶段
在bootstrap之后便进入了event-loop。event-loop第一个阶段便是timers,在这里如果有到时间的Timer,便会触发OnTimeout
,OnTimeout
会触发InternalMakeCallback
从而执行TimersList中的函数。而在执行完后还会触发InternalCallbackScope::Close
,在这个函数中会触发nextTick,在触发nextTick后触发microTasks。setTimeout
简易流程如下:
也正是InternalMakeCallback
和InternalCallbackScope::Close
使得libuv和v8紧紧的联系在了一起,一方面可以通过setTimeout
来设置运行时间;另一方面又可以在setTimeout
的回调中书写js代码。
原文地址:https://github.com/xtx1130/blog/issues/20,欢迎star和fork,如果有错误之处,还请在回复或者issue下面斧正。
by 小菜