Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between process.nextTick and queueMicrotask

Tags:

node.js

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?

like image 224
RaR Avatar asked Apr 02 '19 04:04

RaR


1 Answers

Queues

We can find different queues (show in check priority order after finish a 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 is it means?

The nextTick queue is checked in first place 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 another microtasks)

The use case could be to execute tasks before any microtask, for example, before a promise then and/or catch. It 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 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 chosen decision 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 nextTick queue is first 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 uses 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, take into account this is important to avoid errors (as stated in the behind example).

like image 104
F.bernal Avatar answered Oct 19 '22 17:10

F.bernal