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