Node 11.0.0 adds queueMicrotasks
as experimental. The doc says it is similar to process.nextTick
but the queue is managed by V8 instead of Node.js. What would be the use case to use queueMicrotasks
instead of process.nextTick
? Will there be any performance gain using one over another?

- 3,075
- 3
- 23
- 48
-
duplicate of https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context – Vikash_Singh Apr 02 '19 at 04:52
-
3@VikashSingh This is not a duplicate of that. I checked that question before asking. It explains the difference between microtask and macrotask. But according to the accepted answer, `process.nextTick` also comes under microtasks list – RaR Apr 02 '19 at 04:55
1 Answers
Queues
We can find different queues (shown in check priority order after function/script execution):
- nextTick
- microTasks
- timers (expired)
- immediate
How the queues are checked?
First, nextTick queue is checked to get tasks for its execution, once it is exhausted, the microTasks queue is checked. After finishing the tasks in the microtask queue the process of checking nextTick and microTasks queues are repeated until the queues have been emptied.
The next queue to check is the timers one and in the end the immediate queue.
Differences
It is the same, in the way of both of them are to execute a task just after the execution of the current function or script.
They have different queues. The nextTick's queue is managed by node and the microtask one is managed by v8.
What it means?
The nextTick queue is checked first after the current function/script execution, and then the microTask one.
There is no performance gain, the difference is that the nextTick queue will be checked first after the function/script execution and that must be taken into account. If you never use nextTick and only use queueMicrotask you will have the same behavior of just using nextTick (taking into account that your tasks will be placed in a queue with other microtasks).
The use case could be to execute tasks before any microtask, for example, before a promise's then
and/or catch
. It is worth noting that promises use microtask so, any callback added to then/catch will be added to the microtask queue and its execution will be performed when the nextTick queue is empty.
Example
After the execution of this code:
function task1() {
console.log('promise1 resolved');
}
function task2() {
console.log('promise2 resolved');
process.nextTick(task10);
}
function task3() {
console.log('promise3 resolved');
}
function task4() {
console.log('immediate 1');
}
function task5() {
console.log('tick 1');
}
function task6() {
console.log('tick 2');
}
function task7() {
console.log('microtask 1');
}
function task8() {
console.log('timeout');
}
function task9() {
console.log('immediate 2');
}
function task10() {
console.log('tick3');
queueMicrotask(task11);
}
function task11() {
console.log('microtask 2');
}
Promise.resolve().then(task1);
Promise.resolve().then(task2);
Promise.resolve().then(task3);
setImmediate(task4);
process.nextTick(task5);
process.nextTick(task6);
queueMicrotask(task7);
setTimeout(task8, 0);
setImmediate(task9);
Execution
- nextTick: task5 | task6
- microTasks: task1 | task2 | task3 | task7
- timers: task8
- immediate: task4 | task9
Step 1: execute all tasks in nextTick queue
- nextTick: EMPTY
- microTasks: task1 | task2 | task3 | task7
- timers: task8
- immediate: task4 | task9
output:
- tick 1
- tick 2
Step 2: execute all tasks in microTasks queue
- nextTick: task10
- microTasks: EMPTY
- timers: task8
- immediate: task4 | task9
output:
- tick 1
- tick 2
- promise 1 resolved
- promise 2 resolved
- promise 3 resolved
- microtask 1
Step 3: execute all tasks in nextTick queue (there is a new task added by the execution of microtask (task2))
- nextTick: EMPTY
- microTasks: task11
- timers: task8
- immediate: task4 | task9
output:
- tick 1
- tick 2
- promise 1 resolved
- promise 2 resolved
- promise 3 resolved
- microtask 1
- tick 3
Step 4: execute all tasks in microTasks queue (there is a new task added by the execution of task10)
- nextTick: EMPTY
- microTasks: EMPTY
- timers: task8
- immediate: task4 | task9
output:
- tick 1
- tick 2
- promise 1 resolved
- promise 2 resolved
- promise 3 resolved
- microtask 1
- tick 3
- microtask 2
Step 5: No more tasks in nextTick and microTasks queues, next execute timers queue.
- nextTick: EMPTY
- microTasks: EMPTY
- timers: EMPTY
- immediate: task4 | task9
output:
- tick 1
- tick 2
- promise 1 resolved
- promise 2 resolved
- promise 3 resolved
- microtask 1
- tick 3
- microtask 2
- timeout
Step 6: No more tasks in (expired) timers queue, execute tasks in immediate queue
- nextTick: EMPTY
- microTasks: EMPTY
- timers: EMPTY
- immediate: EMPTY
output:
- tick 1
- tick 2
- promise 1 resolved
- promise 2 resolved
- promise 3 resolved
- microtask 1
- tick 3
- microtask 2
- timeout
- immediate 1
- immediate 2
As we can see there is no performance reason to choose one or another; the choice depends on our needs and what needs to be done and when.
Imagine this code:
let i = 1;
queueMicrotask(() => console.log(i));
process.nextTick(() => i++);
The output will be 2 due to the nextTick queue getting checked first.
but if you do
let i = 1;
queueMicrotask(() => console.log(i));
process.nextTick(() => queueMicrotask(() =>i++));
You will get 1.
With examples, I want to make you see that the use-cases come from your needs of what and when you need to perform a task. And the important thing is taking into account that then/catch callbacks in a promise are microtasks and will be executed after nextTick tasks; this is important for avoiding errors (as shown in the previous example).
-
1An interesting use case I've seen for these separate nextTick and microTasks queues is scheduling a task to run immediately after all microtasks in DataLoader https://github.com/graphql/dataloader/blob/3b0bae94e91453d9a432c02628745252abc5e011/src/index.js#L232 – Richard Scarrott Apr 26 '20 at 10:05
-
1Beware that the timing of immediates relative to timeouts appears non-deterministic. Setting a few zero-timeouts and a few immediates inside a microtask, nextTick job, or immediate task seems execute all immediates (as a block) before, after, or in-between the timeouts – Matthijs Sep 16 '20 at 22:45
-
actually the context doesn't matter, running this as a script a bunch of times: `let msg = []; for(let i = 0; i < 20; i++) { setImmediate(()=>msg.push('i'+i)); setTimeout(()=>msg.push('T'+i)); } process.on('exit',()=>console.log(...msg));` already shows the phenomenon for me (node v12.18.2 on linux on multi-core x86_64) – Matthijs Sep 16 '20 at 23:10
-
1ah, the non-determinism of timeouts is actually [documented](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout) although the core reason isn't: `setTimeout(callback, 0)` is implicitly treated like `setTimeout(callback, 1)` (instead of creating an expired timer like you'd expect), hence behaviour depends on whether you return to the event loop within the same millisecond – Matthijs Sep 17 '20 at 02:17
-
2Explanation is correct. But, can you give some insight into this diagram: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ ? This seems not to match with your explanation. – Praveen Jan 12 '21 at 11:31
-
4@Praveen is right. Your answer is remarkable but the diagram on Node.js Official docs puts timers at the top. Can you shed some light on it please? – thewebjackal Oct 02 '21 at 18:01
-
1@Praveen See this diagram. https://developer.ibm.com/tutorials/learn-nodejs-the-event-loop/#event-loop-phases-overview Step1~4 is the mainline. Step 5 is the start of event loops. – Jehong Ahn May 31 '22 at 02:53