What Are Microtasks?
Microtasks are short functions that execute after the current task completes and only when the JavaScript execution stack is empty, but before the browser returns control to the event loop. Unlike regular tasks (macrotasks) such as those scheduled by setTimeout(), microtasks have higher priority and are processed immediately after the current synchronous execution context finishes.
The microtask queue is processed multiple times per iteration of the event loop, including after handling events and callbacks. When a microtask adds more microtasks by calling queueMicrotask(), those newly-added microtasks execute before the next task runs. This behavior makes microtasks ideal for operations that need to happen "just after" the current work completes.
Understanding microtasks is essential for building responsive user interfaces that execute code predictably and avoid timing-related bugs.
1console.log('Start');2 3queueMicrotask(() => {4 console.log('Microtask executing');5});6 7console.log('End');8 9// Output:10// Start11// End12// Microtask executingTasks vs. Microtasks: Key Differences
Understanding the distinction between tasks and microtasks is essential for writing predictable JavaScript code.
Tasks (Macrotasks)
Tasks are scheduled by:
- Initial JavaScript program execution
- Event callbacks (click, submit, etc.)
setTimeout()andsetInterval()- I/O operations
- Rendering steps
Tasks run one at a time, in the order they were enqueued. After each task completes, the event loop checks for microtasks before moving to the next task.
Microtasks
Microtasks are scheduled by:
queueMicrotask()calls- Promise
.then(),.catch(),.finally()callbacks MutationObservercallbacks- Async/await operations (which use promises internally)
The key difference: Each time a task exits, the event loop runs ALL microtasks until the queue is empty, regardless of how many microtasks were added during processing. This fundamental distinction affects everything from CSS animations timing to React state updates.
| Characteristic | Tasks (Macrotasks) | Microtasks |
|---|---|---|
| Scheduled by | setTimeout, setInterval, events | queueMicrotask, Promise.then |
| Execution order | FIFO, one at a time | All at once until queue empty |
| Priority | Lower | Higher |
| When runs | When event loop reaches them | After sync code, before next task |
| Examples | setTimeout, fetch, click handlers | Promise callbacks, queueMicrotask |
Practical Use Cases
Ensuring Consistent Ordering
One common use case is ensuring consistent execution ordering when working with both synchronous and asynchronous code paths:
// With queueMicrotask - consistent ordering
function processData(data) {
if (data.cached) {
return new Promise(resolve => {
queueMicrotask(() => {
resolve(data.value);
});
});
} else {
return fetchData(data.id).then(result => result.value);
}
}
Batching Operations
Use queueMicrotask() to batch multiple operations:
const pendingOperations = [];
function queueOperation(operation) {
pendingOperations.push(operation);
if (pendingOperations.length === 1) {
queueMicrotask(processBatch);
}
}
function processBatch() {
const operations = [...pendingOperations];
pendingOperations.length = 0;
operations.forEach(op => op());
}
Cleanup After Synchronous Code
Microtasks are ideal for cleanup operations:
function updateComponent() {
this.state = newState;
queueMicrotask(() => {
this.cleanup();
this.triggerAnalytics();
});
}
For landing page optimization, batching operations with queueMicrotask can significantly improve performance by reducing layout thrashing and unnecessary reflows.
1// React warns about unawaited act() calls using queueMicrotask2queueSeveralMicrotasks(() => {3 if (!didAwaitActCall && !didWarnNoAwaitAct) {4 console.error(5 'You called act(async () => ...) without await. '6 );7 }8});Best Practices
When to Use queueMicrotask
Use queueMicrotask() when you need to:
- Ensure code runs after the current synchronous code completes
- Maintain consistent ordering between sync and async code paths
- Defer work until after the current call stack clears
- Schedule cleanup or analytics after a UI update
For teams implementing complex user interfaces, our web development team can help you integrate microtask patterns into your production applications.
When to Use Alternatives
Consider other approaches in these scenarios:
setTimeout(): When you explicitly want to yield to the event looprequestAnimationFrame(): For code that should run before the next paintPromise.resolve().then(): For promise chaining
Performance Considerations
Microtasks execute synchronously until the queue is empty. Recursively adding microtasks can cause an infinite loop:
// WARNING: This creates an infinite loop
queueMicrotask(() => {
queueMicrotask(() => { /* ... */ });
});
Be cautious about the potential for endless microtask processing.
Error Handling
Errors thrown in queueMicrotask() callbacks are reported as standard exceptions rather than as rejected promises:
queueMicrotask(() => {
try {
riskyOperation();
} catch (error) {
handleError(error);
}
});
Proper error handling in microtasks is crucial for maintaining robust website user testing tools and ensuring consistent application behavior.
Summary
The queueMicrotask() API provides a standardized way to schedule code execution at a precise point in the JavaScript event loop. By running microtasks after the current synchronous code completes but before rendering and macrotasks, you gain fine-grained control over execution timing.
Key takeaways:
- Microtasks execute with higher priority than regular tasks
- Use
queueMicrotask()for predictable deferred execution - Avoid infinite microtask recursion
- Prefer
queueMicrotask()overPromise.resolve().then()for non-promise operations
Understanding and leveraging microtasks helps you build more predictable and performant JavaScript applications, especially when implementing landing page templates and other interactive UI components.