NodeJS 事件循环顺序错误

时间:2021-02-05 23:04:47

标签: node.js express event-loop

我的快递代码:

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

问题:为什么微任务在宏任务之后执行?

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

代码:

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

1 个答案:

答案 0 :(得分:1)

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

您的“工作”代码与您在带有 .findOne() 的 Node 程序中的情况不同,因为您正在创建的 Promise 的 executor 函数同步运行(实际上您会看到此代码产生 1, 2, 3 在 Node 中也是如此):

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() 回调被添加到微任务队列并在调用堆栈为空后执行,它会从队列移至堆栈。