node和chrome js执行引擎都是以V8为核心的。因此,在异步事件处理上十分相似。
在node中,异步分为两种,一种是IO的异步,一种是非IO的异步。
异步IO有磁盘IO和网络IO,非IO异步有process.nextTick, promise, setTimeout, setImmediate.。
非异步IO中,process.nextTick, promise等属于microTask, setTimeout, setImmediate 是macroTask。
异步IO的调用需要底层libuv层支持,较为复杂,属于macroTask。
比如,在js中发起一个网络请求时,js代码调用node核心模块,核心模块再调用C++内建模块。内建模块通过libuv进行系统调用。系统调用的过程中,创建相应的请求对象,从js层传入的参数和当前方法都被封装在请求对象中,回调函数被设置在请求对象的oncomplete_sym上,对象包装完毕后,调用QueueUserWorkItem将请求对象推入线程池中等待执行。
js层继续执行后续执行栈中的代码。
在内核层,当线程池中有空闲线程时,调用相应底层函数,发起网络请求。
在v8是单线程的,但是浏览器和操作系统是多线程的(!important)。(意味深长~~)
此时,JavaScript调用立即返回。根据当前的执行栈,继续执行原执行栈的函数。检查microTask中是否任务可以执行,若有,则按照优先级、调用先后顺序依次执行,直到将microTask执行结束。
网络请求对象送入IO线程池等待执行,内核利用端口发起真正的网络请求,并侦听网络端口,一旦接收到网络请求,表示内核层面的网络请求执行完毕。
执行完毕后,会将结果储存在请求对象的result属性上。window下调用PostQueuedCompletionStatus通知IOCP,告知当前对象操作已经完成,并将线程归还线程池。
内核发送IO观察者形成事件,每次Tick时,调用GetQueuedCompletionStatus获取执行状态,得到执行完的IO操作,将请求对象加入IO观察者队列中,将其当作事件处理。
网络IO属于macroTask,这个macroTask放入event loop中。event loop中的异步IO都有相应的观察者,每次tick,检查优先级较高的观察者。
process.nextTick 属于idle观察者,setImmediate是check观察者。idle观察者优于IO观察者,IO观察者优于check观察者。
如有IO观察者回调函数,取出请求对象的result作为参数,在js层回调执行,结束调用。
然后检查microTask,执行所有microTask函数。同时,microTask在event loop中也有优先级。