* 定时器(timer): 在这个阶段执行 setTimeout、setInterval的回调函数 * 待定回调(pending callbacks):某些系统操作(如 TCP 错误类型)执行回调 * idle, prepare:仅系统内部使用。 * 轮询(poll):检索新的 I/O 事件; 执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外) * 检测(check):setImmediate() 回调函数在这里执行。 * 关闭的回调函数(close callbacks):一些关闭的回调函数,如:socket.on('close', ...)。nodejs中也和javascript中一样存在着微任务(micro-task)、宏任务(macro-task),两个任务中执行的内容也有一部分的相似性
微任务:promise的then函数的回调、queueMicrotask、process.nextTick 宏任务: setTimeout、setInterval、io事件、setImmediate、close事件
执行顺序也和javascript中一致,先执行主线程的任务,然后接着执微任务,微任务执行完成再执行宏任务,具体的执行顺序如下。
微任务队列 next tick queue:process.nextTick other tick queue:promise的then函数、queueMicrotask 宏任务队列 timer queue: setTimeout、setInterval poll queue: io事件 check queue: setImmediate close queue: close事件
了解完nodejs中事件循环的执行顺序之后,一起来看看下面这道面试题
async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout0') }, 0) setTimeout(function () { console.log('setTimeout2') }, 300) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick1')); async1(); process.nextTick(() => console.log('nextTick2')); new Promise(function (resolve) { console.log('promise1') resolve(); console.log('promise2') }).then(function () { console.log('promise3') }) console.log('script end')
首先声明了 async1 和 async2函数,只有调用才会被放入调用栈中,所以此时不会执行,往下执行输出 "script start"。
继续向下执行,将 "setTimeout0" 放入 宏任务的 timer queue 中,继续执行,"setTimeout2" 需要延迟300ms执行,不放入 timer queue中,接着把 "setImmediate" 放入 check queue 中,把 "nextTick1" 放入 next tick queue 中。 此时执行函数 async1,输出 "async1 start",在函数 async1 中执行 async2,输出 "async2",把 "async1 end" 放入 other queue当中,此时函数 async1执行完成,继续向下执行,将"nextTick2"放入next tick queue中,排在"nextTick1"后面,继续输出 "promise1",将"promise3"放在other queue中,排在"async1 end"后面,输出"promise2",最后输出"script end",此时主线程的内容都执行完成。 来到微任务队列,微任务队列中先执行 next tick queue 中的内容,依次输出 "nectTick1"、"nectTick2",再执行 other queue 中的内容,依次输出"async1 end"、"promise3"。 最后执行宏任务队列中的任务,先执行 timer queue,输出 "setTimeout0",这里没有io事件(poll),往下执行check quue,输出 setImmediate,没有关闭回调函数阶段( close callbasks),一次事件循环结束,来到第二次、第三次事件循环,此时300ms后,"setTimeout2" 被加入到宏任务队列中的 timer queue,事件循环中没有其他的队列,直接输出 "setTimeout2",事件循环结束。 简单图示如下 再来看另一道面试题setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); });
按照宏任务队列中各任务的执行顺序,setTimeout属于timer queue,setImmediate属于check queue,按理说会先输出setTimeout,但实际情况会是什么样的呢,我们看一下以下输出情况
为什么会这样的情况呢,有时候是setTimeout先输出,有时候是setImmediate先输出,原因在于,setTimeout的回调函数虽然是延迟0毫秒执行,但是 setTimeout的准备时间要长于 event loop 的启动时间,当event loop 开始第一次循环的时候,setTimeout 还没有被放入 timer queue 之中,所以 event loop 先执行 check queue 中的 setImmediate,等待第二次循环的时候,timer queue 中才有setTimeout,此时就会出现 setImmediate 先输出的情况。 nodejs中的事件循环机制有一部分和javascript中事件循环是一致的,如果对javascript事件循环机制还不太熟悉,可以看看这一篇文章,javascript事件循环机制及面试题详解