事件循环阶段Node.js事件循环流程大致如下:┌──────────────────────────────────────────────────────────┐┌──>│计时器││└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────┐││等待回调│└─────────────────────────────────────────────────────────otg────┘│┌──────────────┴────────────────┐││空闲,准备││└──────────────────┬────────────────┘┘────────────────────────┐┐┌────────────────┴──────────────┐│传入:│││民意调查│<──────┤连接、││└──────────────┬────────────────┘│数据等┐└────────────────────┘││检查││└─────────────────────────────────────────────────────────────────────────────────────────┘│┌────────────────┴────────────────┐└──┤关闭回调│└──────────────────────────────────┘每个阶段都有自己的任务队列。当本阶段任务队列执行完毕,或达到最大任务数时,进入下一阶段。timersstage这个阶段会执行setTimeout和setInterval设置的定时任务。当然,这个时机并不准确,但在计时时间过去后,一旦获得执行机会,就会立即执行。在pendingcallbacks阶段,一些与底层系统相关的操作会在这个阶段进行,比如TCP连接返回的错误。当这些错误发生时,它们会被Node推迟到下一个周期。轮询阶段该阶段用于执行IO操作相关的回调。Node会询问操作系统是否触发了新的IO事件,然后执行相应的事件回调。除了定时器事件、setImmediate()和关闭回调之外,几乎所有的操作都会在这个阶段执行。检查阶段这个阶段会执行setImmediate()设置的任务。关闭回调阶段如果一个套接字或句柄(句柄)突然关闭,例如通过socket.destroy(),关闭事件将在这个阶段发送。事件循环的具体执行事件循环初始化后,会按照上图所示的流程进行:首先依次执行timer中的任务和pendingcallback;然后进入idle和prepare阶段,执行Node内部的一些逻辑;然后进入poll轮询阶段。这个阶段会执行所有的IO回调,比如读取文件,网络操作等。poll阶段有一个pollqueue任务队列。这个阶段的执行过程比较长,如下:进入这个阶段会先检查超时定时队列中是否有可执行的任务,如果有则跳转到定时器阶段执行。如果没有定时器任务,它会检查轮询队列任务队列。如果不为空,则遍历并执行所有任务,直到全部执行完或达到最大可执行任务数。poll队列任务队列执行后,会检查setImmediate任务队列中是否有任务。如果是,事件循环将转移到下一个检查阶段。如果没有setImmediate任务,那么Node会在这里等待,等待新的IO回调的到来,并立即执行。注意:这个等待不会一直等下去,而是在达到一个限制条件后,继续下一阶段执行。setTimeout()和setImmediate()的小秘密其实也不算什么秘密,只是我查了资料才知道的。也就是说:在Node中,setTimeout(callback,0)被翻译成setTimeout(callback,1)。详情请参阅此处。setTimeout()和setImmediate()的执行顺序下面两个定时任务的执行顺序在不同情况下是不一致的。setTimeout(function(){console.log('timeout');},0);setImmediate(function(){console.log('immediate');});在普通代码中设置定时器如果在普通代码阶段执行(比如在最外层代码块),设置这两个定时任务,它们的执行顺序不固定。首先我们设置的setTimeout(callback,0)已经转化为setTimeout(callback,1),所以在进入定时器阶段时,会根据当前时间判断计时是否超过1ms。在事件循环进入定时器阶段之前,系统会调用一个方法来更新当前时间。由于系统中同时有其他程序在运行,系统需要等待其他程序进程运行完毕才能获取准确的时间,所以更新的时间可能会有所不同。一定的延迟。更新时间时,如果没有延时且计时小于1ms,则优先执行立即任务;如果有延迟,时间达到1ms的限制,则优先执行超时任务。在IO回调中设置定时器如果我们在IO回调中设置这两个定时器,setImmediate任务会先被执行,原因如下:在进入poll阶段之前,会检查是否有timer定时任务。如果没有timer定时任务,会执行后续的IO回调。我们在IO回调中设置setTimeout定时任务。此时定时器检查阶段已经过去,所以定时器定时任务会顺延到下一个周期。process.nextTick()无论在事件循环的哪个阶段,只要使用process.nextTick()添加回调任务,Node就会在进入下一阶段之前执行nextTickQueue队列中的任务。setTimeout(function(){setImmediate(()=>{console.log('immediate');});process.nextTick(()=>{console.log('nextTick');});},0);//nextTick//immediate上面代码中,nextTick任务总是先执行,因为nextTickQueue中的任务会在循环进入下一阶段之前执行。下面代码的执行结果也是符合预期的。setImmediate(()=>{setTimeout(()=>{console.log('timeout');},0);process.nextTick(()=>{console.log('nextTick');});});//nextTick//超时原文在这里
