浅谈JavaScript的事件循环机制(Event Loop)- Node.js篇|8月更文挑战

394 阅读2分钟

前言

上篇文章我们聊了事件循环机制在浏览器中的表现,本篇我们来聊聊宿主环境换成为Node.js时,会有什么变化。

初探

我们先以宏观视角看下在Node.js中Event Loop长什么样,话不多说,先上图:

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

关于这 6 个阶段,官网描述为:

  • 定时器(timers):本阶段执行已经被 setTimeout()setInterval() 的调度回调函数。
  • 待定回调(pending callbacks):执行延迟到下一个循环迭代的 I/O 回调。
  • idle, prepare:仅系统内部使用。
  • 轮询(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 Node 将在适当的时候在此阻塞。
  • 检测(check)setImmediate() 回调函数在这里执行。
  • 关闭的回调函数(close callbacks):一些关闭的回调函数,如:socket.on('close', ...)

setImmediate

这是一个在Node.js中实现但没有在浏览器中实现的API,我们先来看下它的定义:

该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。

该特性是非标准的,请尽量不要在生产环境中使用它!

看起来和setTimeout没什么区别,不过他们都运行在不同的阶段,setImmediate会在一旦在当前轮询阶段完成时,执行回调用。

我们来看一段比较坑爹的代码:

  setTimeout(() => {
    console.log('setTimeout');
  }, 0);

  setImmediate(() => {
    console.log('setImmediate');
  });

那你觉得是先输出setTimeout还是setImmediate呢?

其实我们在实现运行后发现,都有可能!!!

官方是这么说的:

  • 执行计时器的顺序将根据调用它们的上下文而异。
  • 如果两则都从主模块内调用,则计时器将受到进程性能的约束(这可能会受到计算机上其他正在运行应用程序的影响)。
  • 如果你将这两个函数放入一个 I/O 循环内调用,setImmediate 总是被有限调用。

精简一下,大致意思就是说:它们的执行任务优先级不一样。

process.nextTick()

process.nextTick() 是一个异步任务,它属于微任务。

process.nextTick() 将 callback 添加到"next tick queue"。 在 JavaScript 堆栈上的当前操作运行完成之后,且在允许事件循环继续之前,此队列将被完全排空。 如果递归地调用 process.nextTick(),则可能会创建无限的循环。

小结

最后,我们来罗列下Node.js中的微、宏任务都有哪些:

  • Macrotask:setTimeoutsetIntervalsetImmediate整体代码I/O
  • 常见的 Microtask:process.nextTickPromise 等。

在Node.js中,任务的调度分为了六个阶段,这点和浏览器略有不同。因为所处的阶段不同setTimeout和setImmediate的执行顺序也会因当前上下文环境而改变。

最后打波小广告,美团校招社招内推,不限部门,不限岗位,不限投递数量,海量hc,快来快来~