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