NodeJS事件循环

6 阅读2分钟

Node.js的异步模型

核心:Node.js是单线程的,但它底层借助libuv库实现的事件循环,实现了高效的非阻塞I/O;

  • 单线程:是指执行Javascript的代码的主线程是单线程的,这避免了多线程中令人头痛的资源竞争和状态同步问题
  • 非阻塞I/O:当你发起一个读取文件或者网络请求的异步操作时,主线程不会傻等,而是继续执行后续代码。当操作完成时,对应的回调函数会被放置到任务队列中等待执行。
  • 事件循环:它是一个while(true)死循环,负责不断地检查各个任务队列,并从中取出回调函数放到调用栈中执行。它是连接异步操作结果和Javascript回调的桥梁

事件循环的6大阶段

Node.js的事件循环分为多个阶段,每个阶段都有自己的任务队列。

  • timers阶段(定时器阶段):执行setTimeout,setInterval中到期的回调;延迟时间并不是精确的,它只是表示回调被尽早执行的毫秒数。如果时间循环正阻塞在poll阶段,定时器回调可能会被延迟执行。
  • Pending Callback阶段
  • Idle,prepare阶段,Node.js内部阶段
  • Poll(轮询阶段):
    • 获取新的I/O事件:执行几乎所有的I/O回调(除定时器回调,关闭回调,setImmediate())
    • 处理轮询队列:如果poll队列不为空,事件循环会同步执行队列里的回调,直到队列被清空或达到系统上限。
    • 等待事件:如果po队列为空,他会检查是否有setImmediate()任务
      • 如果有,则结束poll阶段,进入check阶段
      • 如果没有,则阻塞在此阶段,等待新的I/O事件到来,然后等待新的I/O事件的到来,然后立即执行其回调。
  • check(检查阶段):专门执行setImmediate的回调,setImmediate是一个特殊的异步API,他的回调会在poll阶段结束后立即执行
  • close callbacks(关闭回调阶段):执行一些关闭事件的回调,例如socket.on('close',...)

任务优先级:微任务和宏任务

除了上述阶段中的任务(可统称为宏任务,如setTimeout,setImmediate,I/O),Node.js中还有微任务。微任务在每个阶段完成后都会被执行,优先级非常高

  • 宏任务:setTimeout,setInterval,setImmediate,I/O操作
  • 微任务
    • process.nextTick():这是Node.js中优先级最高的微任务,它会在当前操作完成后,立即执行,甚至比Promise的回调还要早
    • Promise的回调:优先级仅次于process.nextTick

    核心执行规则:

    在一个阶段执行完毕之后,事件循环并不会直接进入下一个阶段,而是会先去清空所有的微任务队列(先清空所有的process.nextTick(),再清空所有的Promise),之后才会进入下一个阶段。