NodeJS 事件循环顺序错误

NodeJS event loop wrong order

我的快递代码:

app.use('/test', async (req, res, next) => {
  try {
    setTimeout(() => console.log('setTimeout')); // setTimeout (macrotask)
    User.findOne().then(user => console.log('user')); // promise (microtask)
    console.log('console.log');
  } catch (err) {
    next(err);
  }
});

控制台输出顺序为:

console.log
setTimeout
user

问题:为什么microtask在macrotask之后执行?

例如,在浏览器中,下一个代码以正确的顺序解析:

代码:

setTimeout(function timeout() {
  console.log(3);
}, 0);

let p = new Promise(function (resolve, reject) {
  for (let i = 0; i < 1e10; i++) {}
  resolve();
});

p.then(function () {
  console.log(2);
});

console.log(1);

订单:

1
2
3

微任务需要在宏任务从宏任务队列中出队之前排队,以便它首先 运行。由于 .findOne() 需要一些时间,微任务不会排队,直到 .findOne() 返回的承诺解决,这发生在你的 setTimeout 回调被添加到宏任务队列之后,然后出列到调用堆栈并执行。

您的“工作”代码与您在使用 .findOne() 的 Node 程序中的情况不同,因为您正在同步创建 运行 的 Promise 的执行程序函数(实际上您将看到此代码也在 Node 中生成 1、2、3):

setTimeout(function timeout() { // <--- queue `timeout` callback as a macro-task
  console.log(3);
}, 0);

let p = new Promise(function (resolve, reject) { // run this function synchronously
  for (let i = 0; i < 1e10; i++) {} // <--- wait here for the loop to complete
  resolve();
});
// Only reached once the loop above has complete, as thus, your promise has resolved 
p.then(function () { // <--- `p` has resolved, so we queue the function as a micro-task
  console.log(2);
});

console.log(1); // <--- add the function to call stack (first log to execute)

上面,当我们执行脚本时,setTimeout 回调和 .then() 回调都被添加到它们各自的任务队列中,这样一旦脚本执行完毕,微任务就可以出队入栈,宏任务就可以出栈入栈了。

您的代码不同:

/* 
setTimeout gets added to the callstack, that spins off an API which after 0 m/s adds your callback to the macro-task queue
*/
setTimeout(() => console.log('setTimeout')); 

/* 
.findOne() gets added to the call stack, that spins off an API which after N m/s adds the `.then()` callback to the micro-task queue (N > 0)
*/
User.findOne().then(user => console.log('user')); // promise (microtask)

/* 
While the above API is working in the background, our script can continue on...
console.log() gets added to the call stack and "console.log" gets logged
*/
console.log('console.log');

一旦上面的脚本完成,timeout 回调在宏任务队列中,但 .then() 回调还不在微任务队列中,因为查询是仍在后台执行。 timeout 回调然后被出列到调用堆栈中。一段时间后,您的 .then() 回调被添加到微任务队列并在调用堆栈为空后执行,它会从队列移动到堆栈。