Reputation: 91
My express code:
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);
}
});
The console output order is:
console.log
setTimeout
user
Question: Why microtask is executed after macrotask?
For example, in the browser the next code resolves in correct order:
Code:
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);
Order:
1
2
3
Upvotes: 0
Views: 232
Reputation: 50639
The micro-task needs to be queued before the macro-task has been dequeued off the macro-task queue for it to run first. Since .findOne()
takes some time, the micro-task isn't queued until the promise returned by .findOne()
resolves, which happens after your setTimeout
callback has been added to the macro-task queue, and then dequeue onto the call stack and executed.
Your "working" code is different to your situation in your Node program with .findOne()
, since the executor function for the Promise you're creating runs synchronously (in fact you'll see that this code produces 1, 2, 3 in Node as well):
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)
Above, while we're executing the script, both the setTimeout callback and the .then() callback are added to their respective task-queues so that once the script has finished executing, the micro-task can be dequeued and put onto the stack, and then the macro-task can be deqeued and put onto the stack.
Your code is different:
/*
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');
Once the script above has finished, the timeout
callback is on the macro-task queue, but the .then()
callback isn't on the micro-task queue yet, as the query is still being executed in the background. The timeout
callback then gets dequeued onto the call stack. After some time, your .then()
callback is added to the micro-task queue and executed once the callstack is empty it gets moved from the queue to the stack.
Upvotes: 1