254

I've just finished reading the Promises/A+ specification and stumbled upon the terms microtask and macrotask: see http://promisesaplus.com/#notes

I've never heard of these terms before, and now I'm curious what the difference could be?

I've already tried to find some information on the web, but all I've found is this post from the w3.org Archives (which does not explain the difference to me): http://lists.w3.org/Archives/Public/public-nextweb/2013Jul/0018.html

Additionally, I've found an npm module called "macrotask": https://www.npmjs.org/package/macrotask Again, it is not clarified what the difference exactly is.

All I know is, that it has something to do with the event loop, as described in https://html.spec.whatwg.org/multipage/webappapis.html#task-queue and https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint

I know I should theoretically be able to extract the differences myself, given this WHATWG specification. But I'm sure that others could benefit as well from a short explanation given by an expert.

NicBright
  • 7,289
  • 5
  • 18
  • 19
  • 3
    In short: multiple nested event queues. You could even implement one yourself: `while (task = todo.shift()) task();` – Bergi Sep 18 '14 at 16:36
  • 3
    For someone who wants a bit more details: Secrets of the JavaScript Ninja, 2nd Edition, CHAPTER 13 Surviving events – Ethan Nov 11 '19 at 10:41

6 Answers6

360

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

NicBright
  • 7,289
  • 5
  • 18
  • 19
  • 4
    Although there's a microtask checkpoint in the event loop, this isn't where most developers will encounter microtasks. Microtasks are processed when the JS stack empties. This can happen many times within a task, or even within the render steps of the event loop. – JaffaTheCake Feb 15 '18 at 13:55
  • 5
    `process.maxTickDepth` was removed very long time ago: https://github.com/nodejs/node/blob/d896f03578f2312aaae347de3b5a0b26882effc8/doc/changelogs/CHANGELOG_ARCHIVE.md#20130626-version-0113-unstable – RidgeA Aug 17 '19 at 21:25
  • 2
    you can also use the [queueMicrotask()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/queueMicrotask) method to add a new microtask – ZoomAll Mar 09 '20 at 01:34
  • Thanks @ZoomAll, didn't know queueMicrotask() until now. I've added it to the answer plus links to all the stuff ... – NicBright Mar 09 '20 at 10:34
  • 1
    requestAnimationFrame(rAF) not only generates microtasks. Generally speaking the rAF call creates a [separate queue](https://youtu.be/cCOL7MC4Pl0?t=1657) – ZoomAll Mar 09 '20 at 13:54
  • "One go-around of the event loop will have exactly one task being processed from the macrotask queue" nit-picking but most of the time it will be zero, until the event-loop enters idle. Also, as has been said before, requestAnimationFrame (and UI rendering) are not "tasks", they are part of the event loop processing and are just callbacks, rAF being stored in a map not a queue and other UI events like scroll or resize being just DOM events. This means you can have many non-microtask js jobs executed in the same event loop iteration. – Kaiido Sep 15 '20 at 05:51
  • 1
    “when you need to do stuff asynchronously in a synchronous way?” – Ron Inbar Nov 30 '20 at 21:51
  • 1
    @RonInbar "The importance of microtasks comes in their ability to perform tasks asynchronously but in a specific order." from [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/queueMicrotask). The tasks you schedule will execute later in the loop, but they will execute in the same order in which you schedule them. – David Harkness Apr 03 '21 at 20:49
  • 1
    this article by Jake Archibald helped me understand the difference: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ – Andrii Lukianenko Apr 30 '21 at 14:38
146

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.
wengeezhang
  • 2,911
  • 1
  • 17
  • 10
  • 5
    This is great explanation! Thanks for sharing!. One more thing to mention is in **NodeJs**, `setImmediate()` is macro/task, and `process.nextTick()` is a micro/job. – LeOn - Han Li May 08 '17 at 18:06
  • 7
    What about browser `paint` tasks? In which category would they fit in? – Legends Mar 03 '18 at 17:38
  • I think they would fit in micro tasks (like `requestAnimationFrame`) – Divyanshu Maithani Sep 01 '19 at 04:36
  • Here is the order in which the v8 event loop runs --> Call Stack || Micro Tasks || Task Queue || rAF || Render Tree || Layout || Paint || <----- 1) DOM (new changes), CSSOM (new changes), render tree, layout and paint happen after requestAnimationFrame callback as per the event-loop timers. This is why it is important to finish your DOM operations before rAF as much as you can, rest can go in rAF. P.S: calling rAF will trigger a macro-task execution. – Anvesh Checka Mar 01 '20 at 04:55
  • 4
    I don't know if I am mistaken but I kind of not agreeing with this answer, microtasks runs before macrotask. https://codepen.io/walox/pen/yLYjNRq? – walox May 09 '20 at 13:18
  • 6
    @walox Current script execution is also a macrotask. Once all the synchronous code is finished, event loop will prioritize microtask over macrotask. As with your example, after script executes, timeout callback is in macrotask/callback queue and promise callback is in microtask queue. Since one macrotask is already complete(main script execution), event loop will prioritize promise task over timeout one. Hence the result. – Aakash Thakur Jul 09 '20 at 17:45
  • setTimeout has a minimum ms of 4 and it won't be pushed in task queue till it's time elapsed. If you have a `setTimeout` and a `Promise` callback...the Promise callback will run first and setTimeout will run second. – nullspace Jun 02 '21 at 16:25
102

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 */
Not A Bot
  • 2,474
  • 2
  • 16
  • 33
user1660210
  • 2,306
  • 1
  • 19
  • 16
  • Event loop checks macrotask queue first and then micro tasks, so you're answer is incorrect – Piliponful Jan 06 '22 at 09:49
  • 1
    But why do these 2 queues exist? What is the general distinction between micro & macro tasks? – Minh Nghĩa Jan 10 '22 at 07:47
  • Microtasks enable us to execute certain actions before the UI is re-rendered, thereby avoiding unnecessary UI rendering that might show an inconsistent application state. – sonishubham65 Mar 24 '23 at 08:06
12

Macro tasks include keyboard events, mouse events, timer events (setTimeout) , network events, Html parsing, changing Urletc. A macro task represents some discrete and independent work. micro task queue has higher priority so macro task will wait for all the micro tasks are executed first.

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. Microtasks enable us to execute certain actions before the UI is re-rendered, thereby avoiding unnecessary UI rendering that might show an inconsistent application state.

Separation of macro and microtask enables the event loop to prioritize types of tasks; for example, giving priority to performance-sensitive tasks.

In a single loop iteration, one macro task at most is processed (others are left waiting in the queue), whereas all microtasks are processed.

  • Both task queues are placed outside the event loop, to indicate that the act of adding tasks to their matching queues happens outside the event loop. Otherwise, any events that occur while JavaScript code is being executed would be ignored. The acts of detecting and adding tasks are done separately from the event loop.

  • Both types of tasks are executed one at a time. When a task starts executing, it’s executed to its completion. Only the browser can stop the execution of a task; for example, if the task takes up too much time or memory.

  • All microtasks should be executed before the next rendering because their goal is to update the application state before rendering occurs.

The browser usually tries to render the page 60 times per second, It's accepted that 60 frames per second are the rate at which animations will appear smooth. if we want to achieve smooth-running applications, a single task, and all microtasks generated by that task should ideally complete within 16 ms. If a task gets executed for more than a couple of seconds, the browser shows an “Unresponsive script” message.

reference John Resig-secrets of JS Ninja

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
  • 1
    I was curious about "why" by design, promise uses micro task queue, not macro task queue, and this answer explains it all! Great answer. – Katie Jul 06 '22 at 05:26
4

I created an event loop pseudocode following the 4 concepts:

  1. setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering are part of the macrotask queue. One macrotask item will be processed first.

  2. process.nextTick, Promises, Object.observe, MutationObserver are part of the microtasks queue. The event loop will process all of the items in that queue including ones that were processed during the current iteration.

  3. There is another queue called animation queue which holds animation changes task items which will be processed next. All tasks which exists in this queue will be processed (not including new one which were added during the current iteration). It will be called if it is time for rendering

  4. The rendering pipeline will try to render 60 times a second (every 16 ms)

     while (true){
        // 1. Get one macrotask (oldest) task item
        task = macroTaskQueue.pop(); 
        execute(task);
    
       // 2. Go and execute microtasks while they have items in their queue (including those which were added during this iteration)
        while (microtaskQueue.hasTasks()){
            const microTask = microtaskQueue.pop();
            execute(microTask);
        }
    
        // 3. If 16ms have elapsed since last time this condition was true
        if (isPaintTime()){
        // 4. Go and execute animationTasks while they have items in their queue (not including those which were added during this iteration) 
            const animationTasks = animationQueue.getTasks();
            for (task in animationTasks){
                execute(task);
            }
    
            repaint(); // render the page changes (via the render pipeline)
        }
    }
    
oligofren
  • 20,744
  • 16
  • 93
  • 180
Ran Turner
  • 14,906
  • 5
  • 47
  • 53
  • 1
    This is my favourite explanation. Succinct (code), to the point. Makes it much easier to digest the finer points in other answers once you have the general high level understanding. – oligofren May 25 '23 at 08:16
  • Except that through this over simplification it misses important points, like e.g. the fact that the microtask queue is actually emptied after every call to `execute()`. – Kaiido May 25 '23 at 08:34
0

Your question is answered directly in the Tasks vs. microtasks section of MDN's In depth: Microtasks and the JavaScript runtime environment:

The difference between the task queue and the microtask queue is simple but very important:

  • When executing tasks from the task queue, the runtime executes each task that is in the queue at the moment a new iteration of the event loop begins. Tasks added to the queue after the iteration begins will not run until the next iteration.
  • Each time a task exits, and the execution context stack is empty, each microtask in the microtask queue is executed, one after another. The difference is that execution of microtasks continues until the queue is empty—even if new ones are scheduled in the interim. In other words, microtasks can enqueue new microtasks and those new microtasks will execute before the next task begins to run, and before the end of the current event loop iteration.

The "execution context" relates to user1660210's answer above.