# JavaScript Event Loop Handling of Microtasks and Macrotasks: Complete Guide

> Master JavaScript event loop handling of microtasks and macrotasks for async operations. Understand how Promises and timers execute to build efficient JavaScript applications.

- Repository: [Datawhale/easy-vibe](https://github.com/datawhalechina/easy-vibe)
- Tags: deep-dive
- Published: 2026-05-10

---

**JavaScript processes asynchronous operations through an event loop that prioritizes microtasks (Promise callbacks) over macrotasks (timers, I/O), executing all queued microtasks immediately after the current script and before the next macrotask begins.**

The single-threaded nature of JavaScript relies on the event loop to orchestrate concurrent operations without blocking the main thread. Understanding how the **JavaScript event loop handles microtasks and macrotasks** is essential for predicting execution order and debugging async code. This analysis examines concrete implementations from the `datawhalechina/easy-vibe` repository, specifically its Vue/VitePress documentation components, to demonstrate these concepts in real-world scenarios.

## The Event Loop Architecture

JavaScript environments maintain two distinct queues to manage asynchronous work:

| Queue type | Contents | Execution timing |
|------------|----------|------------------|
| **Microtask queue** | `Promise` callbacks (`.then`, `.catch`, `.finally`), `queueMicrotask`, `process.nextTick` (Node.js) | Immediately after current script, before next macrotask |
| **Macrotask queue** | `setTimeout`, `setInterval`, I/O callbacks, UI rendering, `requestAnimationFrame` | At the start of the next event loop tick |

The event loop follows a strict four-step process:

1. Pull the next **macrotask** from the task queue and execute it.
2. Process **all** queued microtasks that accumulated during that macrotask's execution.
3. Perform rendering updates (if applicable).
4. Return to step 1 for the next iteration.

This architecture ensures that **microtasks always "jump ahead"** of scheduled macrotasks, even when those macrotasks have zero delay.

## Repository Implementation: Easy-Vibe Examples

The `datawhalechina/easy-vibe` repository demonstrates event loop mechanics through practical Vue composables and demo components. These files illustrate how mixing macrotasks and microtasks affects real-world async patterns.

### The delay Helper in composables.js

In [`docs/.vitepress/theme/components/appendix/auth-design/shared/composables.js`](https://github.com/datawhalechina/easy-vibe/blob/main/docs/.vitepress/theme/components/appendix/auth-design/shared/composables.js), the `delay` function creates a Promise that resolves via `setTimeout`:

```javascript
export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

```

When `await delay(0)` executes, it schedules a macrotask (the `setTimeout`), while the `await` itself yields control back to the event loop as a microtask. The Promise resolves in the next tick, but only after all other microtasks complete, demonstrating how macrotasks defer execution while microtasks maintain priority.

### useAsyncState Hook

The same file implements `useAsyncState`, which wraps async operations and manages loading states:

```javascript
const execute = async (fn) => {
  isLoading.value = true
  error.value = null
  try {
    const result = await fn()          // ← creates a microtask
    data.value = result
    return result
  } catch (err) {
    error.value = err
    throw err
  } finally {
    isLoading.value = false
  }
}

```

Each `await` statement generates microtasks for the `.then`/`.catch` branches, ensuring state updates occur before the next macrotask runs. This pattern guarantees that UI updates (triggered by reactive assignments) batch efficiently within the same tick.

### AsyncComparisonDemo Component

The [`AsyncComparisonDemo.vue`](https://github.com/datawhalechina/easy-vibe/blob/main/AsyncComparisonDemo.vue) file in `docs/.vitepress/theme/components/appendix/async-task-queues/` visualizes async framework comparisons using reactive refs and computed properties:

```vue
const selected = ref('Celery')
const currentFw = computed(() => frameworks.find(f => f.name === selected.value))

```

While primarily a UI demonstration, this component relies on the same microtask/macrotask ordering when handling user interactions and data fetching sequences.

## Execution Order Examples

Understanding the practical output requires examining how the event loop sequences mixed operations.

### Basic Priority Demonstration

```javascript
// Synchronous code runs first
console.log('A');

// Schedule a macrotask
setTimeout(() => console.log('B'), 0);

// Schedule a microtask via Promise
Promise.resolve().then(() => console.log('C'));

// More synchronous code
console.log('D');

```

**Output order:**

```

A   // synchronous
D   // synchronous
C   // microtask (drained before next macrotask)
B   // macrotask (next tick)

```

The `setTimeout` callback, despite having zero delay, executes only after the microtask queue empties.

### await delay(0) Pattern

Using the `delay` helper from the Easy-Vibe repository:

```javascript
import { delay } from '@/components/appendix/auth-design/shared/composables.js'

async function demo() {
  console.log('Start')
  await delay(0)      // → microtask waits for macrotask (setTimeout)
  console.log('After await')
}
demo()

```

The console outputs `Start`, then `After await` appears only after the `setTimeout` macrotask resolves in the next event loop iteration.

### Promise Chain vs. Timer

```javascript
console.log('1')
Promise.resolve().then(() => console.log('2'))   // microtask
setTimeout(() => console.log('3'), 0)           // macrotask
console.log('4')

```

**Expected console output:** `1 → 4 → 2 → 3`

## Why Task Priority Matters

Understanding microtask and macrotask distinction delivers three critical benefits:

- **UI Responsiveness**: Updating state in microtasks ensures DOM changes apply before the next render frame, preventing visual stuttering.
- **Performance Optimization**: Batching many microtasks together avoids extra paint cycles that separate macrotasks would trigger.
- **Predictable Ordering**: Knowing that Promises resolve before timers helps eliminate race conditions where `setTimeout` callbacks accidentally execute after async operations complete.

## Summary

- The JavaScript event loop executes **all microtasks** (Promises) before processing the next **macrotask** (timers, I/O).
- Microtasks include `Promise.then`, `queueMicrotask`, and `process.nextTick`, while macrotasks include `setTimeout`, `setInterval`, and rendering events.
- The `delay` helper in `datawhalechina/easy-vibe` demonstrates how `await` creates microtasks that resolve after `setTimeout` macrotasks complete.
- State updates using `useAsyncState` leverage microtask priority to batch UI changes efficiently within single event loop iterations.
- Code execution order follows: synchronous → microtasks → macrotasks, regardless of timer delay values.

## Frequently Asked Questions

### What is the difference between microtasks and macrotasks in JavaScript?

Microtasks are lightweight, immediate jobs like Promise callbacks that execute immediately after the current script finishes, before the browser renders or handles user input. Macrotasks are heavier operations including `setTimeout`, `setInterval`, and I/O events that execute in subsequent event loop ticks, always after the microtask queue drains.

### Why does Promise.resolve() execute before setTimeout(0)?

The event loop specification mandates that the JavaScript engine empty the entire microtask queue after completing the current script and before selecting the next macrotask. Since Promises queue as microtasks and `setTimeout` queues as macrotasks, Promise handlers execute first regardless of the timer's zero-millisecond delay.

### How does the await keyword affect the event loop?

The `await` keyword pauses async function execution and schedules the resumption of that function as a microtask. When the awaited Promise resolves, the engine queues the continuation of the async function to the microtask queue, allowing it to run before any pending macrotasks but after the current synchronous code completes.

### Can microtasks ever block the main thread?

Yes, microtasks can block the main thread if they create recursive Promise resolutions or infinite loops of `queueMicrotask` calls. Because the event loop drains the entire microtask queue before yielding to rendering or macrotasks, excessive microtask generation prevents the browser from updating the UI or processing user input, effectively freezing the interface.