js

梗概:

  • nodejs的异步操作比较多,去除nodejs特有的异步操作,剩下异步的执行顺序与浏览器规范靠拢
    • Node在10的某个版本将循环机制改成和浏览器一致了
  • nodejs将事件循环中宏任务分为6个步骤:
    • 宏任务分为6个步骤:Timers(定时器)、PendingCallbacks(操作系统相关的)、idle,prepare(nodejs内部)、Poll(IO操作)、Check(setimmediate)、ClosingCallBacks(关闭事件)
  • 微任务分为3个优先级:nextTick的回调、promise的回调、setImmediate
  • 每个宏任务之间都要执行所有的微任务

图解

宏任务讲解

定时器(Timer)阶段

这个是事件循环开始的阶段,绑定到这个阶段的队列,保留着定时器(setTimeout, setInterval)的回调。尽管它并没有将回调推入队列中,但是用最小堆来存储计时器并且在到达规定的时间后执行回调。

即将发生的(Pending) i/o 回调阶段

这个阶段执行在事件循环中 pending_queue 里的回调。这些回调是被之前的操作推入的。例如当你尝试往 tcp 中写入一些东西,这个工作完成了,然后回调被推入到队列中。错误处理的回调也在这里。

Idle, Prepare 阶段

尽管名字是空闲(idle),但是每个循环(tick)都运行。Prepare 也在轮询阶段开始之前运行。不管怎样,这两个阶段是 node 主要做一些内部操作的阶段;因此,我们不在这儿讨论。

轮询(Poll)阶段

可能整个事件循环最重要的一个阶段就是 poll phase。这个阶段接受新传入的连接(新的 Socket 建立等)和数据(文件读取等)。我们可以将轮询阶段分成几个不同的部分。

  • 如果 watch_queue(这个队列被绑定到轮询阶段)不为空,它们将会被一个接着一个的执行直到队列为空或者系统到达最大的限制。
  • 一旦队列为空,node 就会等待新的连接。等待或者睡眠的时间取决于多种因素,待会儿我们会讨论。

检查(Check)阶段

轮询的下一个阶段是 check phase,这个专用于 setImmediate 的阶段。为什么需要一个专门的队列来处理 setImmediate 回调。这是因为轮询阶段的行为,待会儿将在流程部分讨论。现在只需要记住检查(check)阶段主要用于处理 setImmediate() 的回调。

关闭(Close)回调

回调的关闭(socket.on(‘close’, (){})) 都在这里处理的,更像一个清理阶段

示例

// Execute this function at the end of the next tick
setTimeout(() => {
    console.log('timeout1');
}, 0);
  
setTimeout(() => {
    console.log('timeout2');
}, 0);
  
// Execute this function after the current tick
setImmediate(() => {
    console.log('immediate1');
});
  
// Execute this function just before the next tick
process.nextTick(() => {
    console.log('nextTick1');
});
  
function promise() {
    return new Promise<void>((resolve, reject) => {
        resolve();
    })
}
  
promise().then(() => {
    console.log('promise1');
})

执行结果

nextTick1
promise1
timeout1
timeout2
immediate1