Break Up Long Tasks
Heavy computations or long loops can block the main thread, delaying event handlers and causing poor Interaction to Next Paint (INP) responsiveness. To prevent this, yield control back to the browser periodically with scheduler.yield(), which pauses an already-running task so the browser can handle pending user input and rendering before continuing.
scheduler.yield() only splits a single running task—it does not order separate work streams ('user-blocking', 'user-visible', 'background'), defer non-urgent background work before it starts, or abort and reprioritize queued tasks via TaskController. DO NOT replace scheduler.postTask() or TaskController with scheduler.yield() alone when scheduling work at different priorities; see schedule-tasks-by-priority (guide not yet published on this site). When a background task scheduled with scheduler.postTask() itself exceeds 50ms, combine both APIs by calling await scheduler.yield() inside the postTask() callback—continuations automatically inherit the enclosing task’s priority and TaskSignal.
Breaking up long tasks
Section titled “Breaking up long tasks”Use scheduler.yield() inside async functions to break up work.
async function processLargeArray(items) { // DO: Set a time-based deadline 50 milliseconds into the future. 50 // milliseconds is the boundary for when a task becomes a long task. let deadline = performance.now() + 50; // 50ms budget
for (const item of items) { // Process the item processItem(item);
// MANDATORY: Yield to the main thread periodically to keep the UI // responsive. This can be done by checking if the deadline set earlier // has been exceeded. When it has been, yield, then reset the deadline // another 50 milliseconds into the future. if (performance.now() >= deadline) { await scheduler.yield(); deadline = performance.now() + 50; } }}Fallback strategies
Section titled “Fallback strategies”Baseline: scheduler
If your Baseline target does not support the Scheduler API, implement a fallback using setTimeout so long loops still yield to the main thread without throwing. (If combining scheduler.yield() with scheduler.postTask(), feature-detect postTask separately as well.)
Fallback for scheduler.yield()
Section titled “Fallback for scheduler.yield()”async function processLargeArrayWithFallback(items) { // DO: Set a time-based deadline 50 milliseconds into the future. let deadline = performance.now() + 50;
for (const item of items) { processItem(item);
// MANDATORY: Yield to the main thread periodically to keep the UI responsive. if (performance.now() >= deadline) { // DO: Feature detect scheduler.yield if ('scheduler' in window && 'yield' in window.scheduler) { await scheduler.yield(); } else { // DO: Fallback to setTimeout for older browsers await new Promise(resolve => setTimeout(resolve, 0)); } deadline = performance.now() + 50; } }}Compatibility notes: Badges like Baseline: … mark web features referenced by this guide. Baseline status describes availability across the core browser set — it does not mean every browser or older version supports the feature. Feature data as published upstream, synced 2026-11-12.
Live demo
Section titled “Live demo”Open the demo standalone · Demo source on GitHub
Counter-example (negative demo) — what this guide tells you to avoid.
Source: GoogleChrome/modern-web-guidance-src (Apache-2.0), upstream commit 179dbc6, synced 2026-11-12. Local changes: macros expanded to links/badges, first heading de-duplicated, demo and this footer added. This is an independent, unofficial site — not a Google publication.