Skip to content

Instantly share code, notes, and snippets.

@hopsoft
Created March 29, 2025 23:44
Show Gist options
  • Save hopsoft/9d13ce9ec3a88d6b7240fec1480006af to your computer and use it in GitHub Desktop.
Save hopsoft/9d13ce9ec3a88d6b7240fec1480006af to your computer and use it in GitHub Desktop.
JavaScript Microtasks

JavaScript Microtasks: A Developer's Guide

What Are Microtasks and Why Should You Care?

As a JavaScript developer, you're likely familiar with the event loop handling asynchronous operations. However, microtasks provide a crucial middle layer between synchronous code execution and the macrotask queue that can significantly impact your application's behavior.

Think of microtasks as high-priority tasks that JavaScript executes immediately after the current synchronous operation, but before the next macrotask (like setTimeout callbacks) or render updates.

Common Sources of Microtasks:

  • Promise resolution callbacks (.then(), .catch(), .finally())
  • The queueMicrotask() API
  • async/await operations

Demonstration

Let's examine a contrived example that shows how microtasks queue and execute. This code intentionally creates a chain of function calls using both synchronous code and microtasks to illustrate execution order:

Click to view the JavaScript…

// microtasks.js
'use strict'

async function a() {
  console.log('a invoked...')
  b()
  console.log('a: waiting on microtasks...')
  await microtasks()
  stack.push({a: `${(performance.now() - start).toFixed(2)}ms`})
  console.log('a: done', stack)
}

/**
 * This style exhibits the same behavior as the async function
 * Use-case may dictate which style to use
function a() {
  console.log('a invoked...')
  b()
  console.log('a: waiting on microtasks...')
  microtasks().then(() => {
    stack.push({a: `${(performance.now() - start).toFixed(2)}ms`})
    console.log('a: done', stack)
  })
}
 */

function b() {
  console.log('b: invoked...')
  queueMicrotask(() => c())
  stack.push({b: `${(performance.now() - start).toFixed(2)}ms`})
  console.log('b: done', stack)
}

function c() {
  console.log('c: invoked...')
  queueMicrotask(() => d())
  stack.push({c: `${(performance.now() - start).toFixed(2)}ms`})
  console.log('c: done', stack)
}

function d() {
  console.log('d: invoked...')
  stack.push({d: `${(performance.now() - start).toFixed(2)}ms`})
  console.log('d: done', stack)
}

async function microtasks() {
  await Promise.resolve()
}

const start = performance.now()
const stack = []

a()

Challenge: Before Running the Code

Take a moment to predict:

  1. Will function c execute before a finishes?
  2. What order will the timestamps appear in?
  3. How many console.log statements will run before microtasks begin?

Actual Output

Click to view the Output…

node microtasks.js
a invoked...
b: invoked...
b: done [ { b: '3.91ms' } ]
a: waiting on microtasks...
c: invoked...
c: done [ { b: '3.91ms' }, { c: '4.17ms' } ]
d: invoked...
d: done [ { b: '3.91ms' }, { c: '4.17ms' }, { d: '4.34ms' } ]
a: done [ { b: '3.91ms' }, { c: '4.17ms' }, { d: '4.34ms' }, { a: '4.69ms' } ]

Understanding the Flow

The sequence diagrams help visualize exactly how JavaScript processes these operations:

Click to view the Sequence Diagram…

┌────────┐   ┌────────┐   ┌────────┐   ┌────────────┐   ┌────────┐   ┌────────┐
│ main   │   │ a()    │   │ b()    │   │ microtask  │   │ c()    │   │ d()    │
│        │   │        │   │        │   │ queue      │   │        │   │        │
└───┬────┘   └───┬────┘   └───┬────┘   └─────┬──────┘   └───┬────┘   └───┬────┘
    │            │            │              │              │            │
    01⟩─────────▶︎│            │              │              │            │
    │ call a     │            │              │              │            │
    │            02⟩─────────▶︎│              │              │            │
    │            │ call b     │              │              │            │
    │            │            03⟩───────────▶︎│              │            │
    │            │            │ queue c      │              │            │
    │            │            │              │              │            │
    │            │◀︎─────────⟨04              │              │            │
    │            │     return │              │              │            │
    │            │            │              │              │            │
    │            05⟩────────────────────────▶︎│              │            │
    │            │ await microtasks          │              │            │
    │            │            │              │              │            │
    │            │            │              06⟩───────────▶︎│            │
    │            │            │              │ call c       │            │
    │            │            │              │              │            │
    │            │            │              │◀︎───────────⟨07            │
    │            │            │              │      queue d │            │
    │            │            │              │              │            │
    │            │            │              │◀︎───────────⟨08            │
    │            │            │              │       return │            │
    │            │            │              │              │            │
    │            │            │              09⟩────────────────────────▶︎│
    │            │            │              │ call d       │            │
    │            │            │              │              │            │
    │            │            │              │◀︎────────────────────────⟨10
    │            │            │              │              │     return │
    │            │◀︎────────────────────────⟨11              │            │
    │            │           microtasks done │              │            │
    │            │            │              │              │            │
    │◀︎─────────⟨12            │              │              │            │
    │     return │            │              │              │            │
    │            │            │              │              │            │
Click to view the Execution Tree…

01. Main Calls a()
    │
    ├─ logs "a invoked..."
    │
    02. └─ calls b()
        │   ├─ logs "b: invoked..."
        │   │
        03. ├─ queues c() as microtask
        │   ├─ adds timestamp {b: 3.91ms}
        │   └─ logs "b: done"
        │
    04. returns to a()
        │
    05. └─ logs "a: waiting..."
        └─ awaits microtasks

    Microtask Queue Processing:
    │
    06. └─ c() executes
        │   ├─ logs "c: invoked..."
        │   │
        07. ├─ queues d() as microtask
        │   ├─ adds timestamp {c: 4.17ms}
        08. └─ logs "c: done"
        │
    09. └─ d() executes
        │   ├─ logs "d: invoked..."
        │   ├─ adds timestamp {d: 4.34ms}
        10. └─ logs "d: done"
        │
    11. └─ microtasks complete
        │   a() resumes
        │   ├─ adds timestamp {a: 4.69ms}
        │   └─ logs "a: done"
        │
12. returns to main

Key Takeaways for Developers

Understanding microtask behavior is crucial for:

  1. Predictable Async Code: Microtasks execute in a guaranteed order before the next macrotask or render
  2. Performance Optimization: Microtasks run within the same tick of the event loop
  3. Debugging: When troubleshooting async code, remember microtasks take priority over macrotasks
  4. Framework Development: Many modern frameworks rely on microtask timing for state updates

Common Gotchas

  • Microtasks can delay macrotasks indefinitely if they keep spawning new microtasks
  • Promise chains create microtasks for each .then() callback
  • async/await syntax implicitly creates microtasks

This knowledge is especially valuable when building complex applications or working with modern JavaScript frameworks that heavily utilize the Promise API and async operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment