Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between microtask and macrotask within an event loop context

People also ask

What is the difference between microtask and Macrotask?

A macro task represents some discrete and independent work. Microtasks, are smaller tasks that update the application state and should be executed before the browser continues with other assignments such as re-rendering the UI. Microtasks include promise callbacks and DOM mutation changes.

What are Macrotask in event loop?

In comparison, the macro-task queue has a lower priority. Macro-tasks include parsing HTML, generating DOM, executing main thread JavaScript code and other events such as page loading, input, network events, timer events, etc. Examples: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI Rendering.

What is an event loop and callback queue and microtask queue?

Microtask Queue gets the callback functions coming through Promises and Mutation Observer. Callback Queue has lesser priority than Microtask Queue of fetching the callback functions to Event Loop. Microtask Queue has higher priority than Callback Queue of fetching the callback functions to Event Loop.

What is microtask in JavaScript?

A microtask is a short function which is executed after the function or program which created it exits and only if the JavaScript execution stack is empty, but before returning control to the event loop being used by the user agent to drive the script's execution environment.


One go-around of the event loop will have exactly one task being processed from the macrotask queue (this queue is simply called the task queue in the WHATWG specification). After this macrotask has finished, all available microtasks will be processed, namely within the same go-around cycle. While these microtasks are processed, they can queue even more microtasks, which will all be run one by one, until the microtask queue is exhausted.

What are the practical consequences of this?

If a microtask recursively queues other microtasks, it might take a long time until the next macrotask is processed. This means, you could end up with a blocked UI, or some finished I/O idling in your application.

However, at least concerning Node.js's process.nextTick function (which queues microtasks), there is an inbuilt protection against such blocking by means of process.maxTickDepth. This value is set to a default of 1000, cutting down further processing of microtasks after this limit is reached which allows the next macrotask to be processed)

So when to use what?

Basically, use microtasks when you need to do stuff asynchronously in a synchronous way (i.e. when you would say perform this (micro-)task in the most immediate future). Otherwise, stick to macrotasks.

Examples

macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
microtasks: process.nextTick, Promises, queueMicrotask, MutationObserver


Basic concepts in spec:

  • An event loop has one or more task queues.(task queue is macrotask queue)
  • Each event loop has a microtask queue.
  • task queue = macrotask queue != microtask queue
  • a task may be pushed into macrotask queue,or microtask queue
  • when a task is pushed into a queue(micro/macro),we mean preparing work is finished,so the task can be executed now.

And the event loop process model is as follows:

when call stack is empty,do the steps-

  1. select the oldest task(task A) in task queues
  2. if task A is null(means task queues is empty),jump to step 6
  3. set "currently running task" to "task A"
  4. run "task A"(means run the callback function)
  5. set "currently running task" to null,remove "task A"
  6. perform microtask queue
    • (a).select the oldest task(task x) in microtask queue
    • (b).if task x is null(means microtask queues is empty),jump to step (g)
    • (c).set "currently running task" to "task x"
    • (d).run "task x"
    • (e).set "currently running task" to null,remove "task x"
    • (f).select next oldest task in microtask queue,jump to step(b)
    • (g).finish microtask queue;
  7. jump to step 1.

a simplified process model is as follows:

  1. run the oldest task in macrotask queue,then remove it.
  2. run all available tasks in microtask queue,then remove them.
  3. next round:run next task in macrotask queue(jump step 2)

something to remember:

  1. when a task (in macrotask queue) is running,new events may be registered.So new tasks may be created.Below are two new created tasks:
    • promiseA.then()'s callback is a task
      • promiseA is resolved/rejected:  the task will be pushed into microtask queue in current round of event loop.
      • promiseA is pending:  the task will be pushed into microtask queue in the future round of event loop(may be next round)
    • setTimeout(callback,n)'s callback is a task,and will be pushed into macrotask queue,even n is 0;
  2. task in microtask queue will be run in the current round,while task in macrotask queue has to wait for next round of event loop.
  3. we all know callback of "click","scroll","ajax","setTimeout"... are tasks,however we should also remember js codes as a whole in script tag is a task(a macrotask) too.

I think we can't discuss event loop in separation from the stack, so:

JS has three "stacks":

  • standard stack for all synchronous calls (one function calls another, etc)
  • microtask queue (or job queue or microtask stack) for all async operations with higher priority (process.nextTick, Promises, Object.observe, MutationObserver)
  • macrotask queue (or event queue, task queue, macrotask queue) for all async operations with lower priority (setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

And event loop works this way:

  • execute everything from bottom to top from the stack, and ONLY when the stack is empty, check what is going on in queues above
  • check micro stack and execute everything there (if required) with help of stack, one micro-task after another until the microtask queue is empty or don't require any execution and ONLY then check the macro stack
  • check macro stack and execute everything there (if required) with help of the stack

Micro stack won't be touched if the stack isn't empty. The macro stack won't be touched if the micro stack isn't empty OR does not require any execution.

To sum up: microtask queue is almost the same as macrotask queue but those tasks (process.nextTick, Promises, Object.observe, MutationObserver) have higher priority than macrotasks.

Micro is like macro but with higher priority.

Here you have "ultimate" code for understanding everything.


console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
    setTimeout(() => {
        console.log('stack [4]')
        setTimeout(() => console.log("macro [5]"), 0);
        p.then(() => console.log('micro [6]'));
    }, 0);
    console.log("stack [7]");
});

console.log("macro [8]");
/* Result: stack [1] macro [8] stack [7], stack [7], stack [7] macro [2] macro [3] stack [4] micro [6] stack [4] micro [6] stack [4] micro [6] macro [5], macro [5], macro [5] -------------------- but in node in versions < 11 (older versions) you will get something different stack [1] macro [8] stack [7], stack [7], stack [7] macro [2] macro [3] stack [4], stack [4], stack [4] micro [6], micro [6], micro [6] macro [5], macro [5], macro [5] more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3 */

JavaScript is high-level, single-threaded language, interpreted language. This means that it needs an interpreter which converts the JS code to a machine code. interpreter means engine. V8 engines for chrome and webkit for safari. Every engine contains memory, call stack, event loop, timer, web API, events, etc.

Event loop: microtasks and macrotasks

The event loop concept is very simple. There’s an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks

Tasks are set – the engine handles them – then waits for more tasks (while sleeping and consuming close to zero CPU). It may happen that a task comes while the engine is busy, then it’s enqueued. The tasks form a queue, so-called “macrotask queue

Microtasks come solely from our code. They are usually created by promises: an execution of .then/catch/finally handler becomes a microtask. Microtasks are used “under the cover” of await as well, as it’s another form of promise handling. Immediately after every macrotask, the engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else

enter image description here