Understanding the JavaScript Event Loop: Macrotasks, Microtasks, and Vue NextTick

Macrotasks and Microtasks

When a browser loads a standard <script> tag (ignoring attributes like defer), the execution of that script creates the initial macrotask. Within this execution context, various operations occur. For instance, if you set up a click listener or initiate a network request (like fetch), the callback functions for these events are queued to run as separate macrotasks later.

However, if your code utilizes APIs like Promise.then() or MutationObserver, their callback functions behave differently. These are designated as microtasks. They do not wait for the next event loop cycle; instead, they execute immediately after the current macrotask finishes but before the browser proceeds to the rendering phase.

The lifecycle of a running application follows this recurring pattern:

Macrotask (e.g., Script execution) 
  -> Microtask Queue Flush 
  -> Rendering 
  -> Macrotask 
  -> Microtask Queue Flush 
  -> Rendering ...

This continuous cycle is known as the Event Loop.

Practical Execution Example

To visualize the priority, consider the following snippet. Note that setTimeout creates a macrotask, while Promise callbacks create microtasks.

// Immediate microtasks
Promise.resolve().then(() => console.log('Micro A'));
Promise.resolve().then(() => console.log('Micro B'));

// Macrotasks scheduled for later
setTimeout(() => {
    console.log('Macro X');
    Promise.resolve().then(() => console.log('Inner Micro X'));
}, 0);

setTimeout(() => console.log('Macro Y'), 0);
setTimeout(() => console.log('Macro Z'), 0);

// More immediate microtasks
Promise.resolve().then(() => console.log('Micro C'));
Promise.resolve().then(() => {
    console.log('Micro D');
    Promise.resolve().then(() => console.log('Inner Micro D'));
});
Promise.resolve().then(() => console.log('Micro E'));

The execution logic breaks down as follows:

  1. Initial Macrotask: The script runs. It schedules three setTimeout callbacks (Macro X, Y, Z) and several Promise callbacks (Micro A, B, C, D, E).
  2. Microtask Queue Draining: Before any rendering or next macro occurs, the engine empties the microtask queue. It prints Micro A, Micro B, Micro C. When it hits Micro D, that function queues Inner Micro D. Because the microtask queue must be fully cleared before moving on, Inner Micro D executes immediately after Micro D, followed by Micro E.
  3. Next Macrotask: The Event Loop picks the first setTimeout (Macro X). It prints Macro X and queues Inner Micro X. Once the macro finishes, the microtask queue is flushed again, printing Inner Micro X.
  4. Subsequent Macrotasks: Finally, Macro Y and Macro Z execute in their respective turns.

A key detail often missed is the behavior of the Promise constructor. If we add the following line to the end of the script:

new Promise((resolve) => console.log('Sync Constructor Logic'));

Despite being at the bottom of the file, Sync Constructor Logic prints first. This is because the function passed to the new Promise executor runs synchronously within the current macrotask, not as a microtask.

Implications for Vue's $nextTick

The browser's event loop order (Macrotask -> Microtask -> Render) explains how Vue.js updates the DOM. Vue batches DOM updates and typically applies them within a macrotask or tick. However, Vue.nextTick (or this.$nextTick) allows you to run code after that DOM update has occurred but before the browser has actually painted the pixels to the screen.

Since microtasks run after the synchronous update logic but before rendering, $nextTick utilizes the microtask queue (or a fallback like setTimeout if microtasks aren't available) to ensure you have access to the updated DOM properties.

Consider this demonstration:

// Assume the background is currently yellow
document.body.style.backgroundColor = 'yellow'; 

// Update to red (Simulating a Vue reactivity update)
document.body.style.backgroundColor = 'red';

// Check the value inside a microtask
Promise.resolve().then(() => {
    let startTime = Date.now();
    // Block the thread for 3 seconds
    while(Date.now() - startTime < 3000) {
        // During this entire 3s block, the console shows 'red'
        // but the visual page remains 'yellow' until this thread releases
        console.log(document.body.style.backgroundColor); 
    }
});

This proves that the DOM property is updated to 'red' immediately in the JavaScript context (accessible in the microtask), eventhough the visual rendering happens after the microtask queue is empty.

Tags: javascript Event Loop Microtasks Macrotasks Vue.js

Posted on Sun, 14 Jun 2026 16:24:15 +0000 by cspgsl