async function processData(data) {
statusEl.textContent = 'Loading...'; // DOM write
// Immediately await a microtask β still blocks render!
await Promise.resolve();
const result = heavyComputation(data); // blocks for 2 seconds
statusEl.textContent = 'Done: ' + result;
}async function processData(data) {
statusEl.textContent = 'Loading...';
// setTimeout(0) = macrotask β yields to the browser's render phase
await new Promise(resolve => setTimeout(resolve, 0));
// Now the browser has had a chance to paint "Loading..."
const result = heavyComputation(data);
statusEl.textContent = 'Done: ' + result;
}
// Even better: use requestAnimationFrame to align with render
async function processDataRaf(data) {
statusEl.textContent = 'Loading...';
await new Promise(resolve => requestAnimationFrame(resolve));
const result = heavyComputation(data);
statusEl.textContent = 'Done: ' + result;
}Bug: Promise.resolve() is a microtask β it runs before the browser has a chance to render. The repaint is still blocked by the heavy computation that runs right after.
Explanation: Microtasks (Promise.resolve) run before the browser paint. To yield for rendering, use setTimeout(0) which is a macrotask β the browser gets a render frame between macrotasks.
Key Insight: To yield to the browser render phase, use setTimeout(0) or requestAnimationFrame β NOT Promise.resolve(). Microtasks drain before any painting occurs.