跳到主要内容

Scheduling Timers

2024年10月14日
柏拉文
越努力,越幸运

一、setImmediate(callback[, ...args])


1.1 认识

setImmediate(callback[, ...args])I/O 事件的回调之后调度 callback 的“立即”执行。当多次调用 setImmediate() 时,则 callback 函数会按照它们的创建顺序排队执行。 每次事件循环迭代都会处理整个回调队列。 如果立即定时器从正在执行的回调中排队,则直到下一次事件循环迭代才会触发该定时器。

1.2 语法

const immediateID = setImmediate(()=>{},arg1,arg2,……,argn);
  • callback: 在本轮 Node.js 事件循环结束时调用的函数
  • ...args: 调用 callback 时要传入的可选参数。
  • immediateID: 用于 clearImmediate()

二、setInterval(callback[, delay[, ...args]])


2.1 认识

setInterval(callback[, delay[, ...args]])delay 毫秒调度重复执行 callback

2.2 语法

const intervalID = setInterval(()=>{},delay,arg1,arg2,……,argn);
  • callback: 当定时器结束时调用的函数。
  • delay: 调用 callback 之前等待的毫秒数。 默认值: 1。当 delay 大于 2147483647 或小于 1 时,则 delay 将设置为 1。 非整数延迟被截断为整数。
  • arg1,arg2,……,argn: 调用 callback 时要传入的可选参数。
  • intervalID: 用于 clearInterval()

三、setTimeout(callback[, delay[, ...args]])


3.1 认识

setTimeout(callback[, delay[, ...args]]) delay 毫秒后调度单次的 callback 的执行。

3.2 语法

const timeoutID = setTimeout(()=>{},delay,arg1,arg2,……,argn);
  • callback: 当定时器结束时调用的函数。
  • delay: 调用 callback 之前等待的毫秒数。 默认值: 1。当 delay 大于 2147483647 或小于 1 时,则 delay 将设置为 1。 非整数延迟被截断为整数。
  • arg1,arg2,……,argn: 调用 callback 时要传入的可选参数。
  • intervalID: 用于 clearInterval()

3.3 对比

一、setImmediate()setTimeout()对比

  • 针对调用时机: setImmediate()setTimeout() 很类似,但是基于被调用的时机,他们也有不同表现:

    • setImmediate() 在当前poll阶段完成后check阶段执行脚本
    • setTimeout() 在最小阈值(ms 单位)过后运行脚本。poll阶段为空闲时,且设定时间到达后执行,但它在timer阶段执行
  • 针对调用顺序: 执行计时器的顺序将根据调用它们的上下文而异

    • 如果二者都从主模块内调用: 则计时器将受进程性能的约束(这可能会受到计算机上其他正在运行应用程序的影响),执行两个计时器的顺序是非确定性的

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

      setImmediate(() => {
      console.log('immediate');
      });
    • 如果二者都在I/O循环内调用: setImmediate 总是被优先调用

      const fs = require('fs');

      fs.readFile(__filename, () => {
      setTimeout(() => {
      console.log('timeout');
      }, 0);
      setImmediate(() => {
      console.log('immediate');
      });
      });
    • 使用 setImmediate() 相对于 setTimeout() 的主要优势: 如果 setImmediate() 是在 I/O 周期内被调度的,那它将会在其中任何的定时器之前执行,跟这里存在多少个定时器无关

二、process.nextTick()setImmediate() 对比

  • 从执行时机上来看:

    • process.nextTick() 在同一个阶段立即执行。
    • setImmediate() 在事件循环的接下来的迭代或 'tick' 上触发。

    实质上,这两个名称应该交换,因为 process.nextTick()setImmediate() 触发得更快,但这是过去遗留问题,因此不太可能改变。我们建议开发人员在所有情况下都使用 setImmediate(),因为它更容易理解。

  • 从事件循环阶段来看:

    • process.nextTick: process.nextTick 从技术上讲不是事件循环的一部分。process.nextTick()方法将 callback 添加到next tick队列。 一旦当前事件轮询队列的任务全部完成,在next tick队列中的所有callbacks会被依次调用,并且优先于其他 microtask 执行
    • setImmediate
  • 从设计理念的角度来看:

    • process.nextTick: process.nextTick 可以保证回调在其余代码(比如说 初始化所有的变量、函数)之后、事件循环之前执行回调函数。为了实现这一点,JS 调用栈被允许展开,然后立即执行提供的回调,允许进行递归调用 process.nextTick(),而不触碰 RangeError: 超过 V8 的最大调用堆栈大小 限制。
    • setImmediate:
  • 从使用场景、存在意义来看(为什么要使用):

    • process.nextTick:

      • 允许用户处理错误,清理任何不需要的资源,或者在事件循环继续之前重试请求
      • 有时有让回调在栈展开后,但在事件循环继续之前运行的必要
    • setImmediate:

参考资料


Node.js 中文网