commonJs 和 ESM 中的 process.nextTick 与queueMicrotask。执行顺序是什么?

Art*_*nko 9 javascript event-loop commonjs node.js es6-modules

假设我们有一个包含以下 JS 代码的文件:

process.nextTick(()=>{
    console.log('nextTick')
})

queueMicrotask(()=>{
    console.log('queueMicrotask')
})

console.log('console.log')
Run Code Online (Sandbox Code Playgroud)

我们已经"type": "commonjs"在 package.json 中设置了模块系统

我们期望控制台输出什么?Node.js 官方文档说:

在 Node.js 事件循环的每一轮中,process.nextTick() 队列始终在微任务队列之前处理

所以,在控制台中我们期望

console.log
nextTick
queueMicrotask
Run Code Online (Sandbox Code Playgroud)

那行得通。直到我将模块系统更改为"type": "module". 在那次更改之后,我不断地在 process.nextTick 之前执行queueMicrotask。控制台输出为:

console.log
queueMicrotask
nextTick
Run Code Online (Sandbox Code Playgroud)

有人能解释这种行为吗?我假设这种行为在某种程度上与模块评估和执行过程有关,并且 nextTick queueMicrotask 以某种方式进入不同的事件循环滴答,因为模块意味着异步执行(在浏览器中)。尽管如此,这种猜测还是非常不稳定,从我的角度来看,这是不合逻辑的。尽管如此,我仍然没有一个合理的解释。

Kai*_*ido 5

这是因为,当作为 ESM 运行时,脚本实际上已经处于微任务阶段。因此,从那里排队的新微任务将在“”回调之前执行nextTick


两个队列在这里的行为相同,因为它们在完成之前都被“实时”清空,因此任何新的排队回调都将在访问任何其他队列之前执行。

queueMicrotask(() => {
  let winner;
  console.log("in microtask, the winner is");
  queueMicrotask(() => {
    if (!winner) console.log(winner = "microtask");
  });
  process.nextTick(() => {
    if (!winner) console.log(winner = "nextTick");
  });
});
process.nextTick(() => {
  let winner;
  console.log("in nextTick, the winner is");
  queueMicrotask(() => {
    if (!winner) console.log(winner = "microtask");
  });
  process.nextTick(() => {
    if (!winner) console.log(winner = "nextTick");
  });
});
Run Code Online (Sandbox Code Playgroud)

无论主脚本如何评估,上面的脚本将始终输出“ In microtask the winner is microtask ”和“ In nextTick the winner is nextTick ”。(但这两个语句的顺序将会改变)。


在 Node 中,ESM 模块脚本实际上是通过async/await函数进行评估的:

 async run() {
  await this.instantiate();
  const timeout = -1;
  const breakOnSigint = false;
  try {
    await this.module.evaluate(timeout, breakOnSigint);
Run Code Online (Sandbox Code Playgroud)

因此,这意味着当我们的脚本运行时,我们已经进入微任务阶段,因此从那里排队的新微任务将首先执行。

但当作为 CommonJS 运行时,我们仍处于轮询阶段。入口点就在这里,然后一切直到实际评估都是同步的。而从 poll 阶段来看,nextTick将会战胜 microtask 阶段:

setImmediate(() => {
  let winner;
  console.log("in poll phase, the winner is");
  queueMicrotask(() => {
    if (!winner) console.log(winner = "microtask");
  });
  process.nextTick(() => {
    if (!winner) console.log(winner = "nextTick");
  });
});
Run Code Online (Sandbox Code Playgroud)

无论主脚本如何评估,这将始终输出“在轮询阶段获胜者是 nextTick ”。