Reputation: 111
when I run
process.nextTick(() => console.log(8))
Promise.resolve("hi").then(t => console.log(t))
console.log(7);
Output is 7 8 hi
The output is expected because process.nextTick runs after the stack is empty and before we start processing the task queue. But when I run
function main() {
console.log(1);
console.log(2);
func();
console.log(6);
}
function func() {
console.log(4);
console.log(5);
Promise.resolve() // callback gets pushed to microtask queue
.then(() => { // this callback is in callstack now after logging 6
process.nextTick(() => console.log(8))
Promise.resolve("hi").then(t => console.log(t))
console.log(7);
})
}
Output is 1 2 4 5 6 7 hi 8
Why does the last part log 7 hi 8 instead of 7 8 hi?
Upvotes: 2
Views: 882
Reputation: 29282
Callback function passed to the process.nextTick
is invoked asynchronously and is executed after the current operation has finished.
In your case, the current operation is the processing of the micro-task queue. Once the processing of the micro-task queue is started, it continues until all the micro-tasks in the queue have been processed.
Any micro-tasks enqueued by the existing micro-tasks in the micro-task queue are also processed, which can lead to infinite loop if you are not careful.
In the following code
Promise.resolve()
.then(() => {
process.nextTick(() => console.log(8))
Promise.resolve("hi").then(t => console.log(t))
console.log(7);
})
after scheduling the callback passed to process.nextTick
, current micro-task (callback function of then()
) queues another micro-task:
Promise.resolve("hi").then(t => console.log(t))
This micro-task will also be processed in the current phase of processing the micro-task queue.
Once the micro-tasks have been processed, event loop will process the callback function passed to the process.nextTick
before moving to the next phase of processing different callbacks in different queues.
For further details, see: The Node.js Event Loop, Timers, and process.nextTick()
Upvotes: 1
Reputation: 1814
This happens because you are adding promise callbacks (microtasks) recursively.
Promise callbacks are microtasks, and they are added to the microtasks queue managed by v8.
When v8 executes the microtasks queue it will find your first resolved promise callback as the only microtask in the queue. But when v8 executes the callback, another microtask is immediately added to the queue by Promise.resolve("hi").then(t => console.log(t))
. Since v8 is still processing microtasks, it will process the second promise resolve callback before moving on.
This will become quite clear if you schedule another microtask inside your second Promise.resolve
callback as follows:
function main() {
console.log(1);
console.log(2);
func();
console.log(6);
}
function func() {
console.log(4);
console.log(5);
Promise.resolve() // callback gets pushed to microtask queue
.then(() => { // this callback is in callstack now after logging 6
process.nextTick(() => console.log(8))
Promise.resolve("hi").then(t => {
console.log(t)
Promise.resolve("there").then(t => console.log(t))
})
console.log(7);
})
}
The above code will yield the following output:
1
2
4
5
6
7
hi
there
8
I wrote the article series linked by @jfriend00 (Thanks for linking the article). However, this behaviour you observed is consistent across all NodeJS versions. (at least currently stable Node versions at the time of this writing.)
Scheduling microtasks recursively is risky, and it's documented in MDN.
Warning: Since microtasks can themselves enqueue more microtasks, and the event loop continues processing microtasks until the queue is empty, there's a real risk of getting the event loop endlessly processing microtasks. Be cautious with how you go about recursively adding microtasks.
Upvotes: 2