FE Lead prep · ref 11 · §2.5 · one thread, run-to-completion · the model under INP/jank
setTimeout/setInterval, I/O, message/UI events (click), postMessage.Promise.then/catch/finally, await continuation, queueMicrotask, MutationObserver.console.log('A');
setTimeout(()=>console.log('B'),0); // macrotask
Promise.resolve().then(()=>console.log('C')); // microtask
console.log('D');
// → A D C B // sync first; microtask C before macrotask B (0ms irrelevant)
await = microtask in disguise: code before 1st await is sync; everything after await is the continuation → runs as a microtask. Post-await line is deferred.| API | Runs | For |
|---|---|---|
queueMicrotask/.then | microtask, before paint | finish current op before frame (batch state). Not heavy work. |
setTimeout(fn,0) | macrotask, next turn (~4ms clamp nested) | yield to render/input; defer non-urgent work |
requestAnimationFrame | just before next paint (~60fps) | visual/animation in sync w/ frame (L07 jank) |
requestIdleCallback | main thread idle (deadline) | low-pri bg: analytics, prefetch — never block input |
scheduler.postTask/.yield | prioritised; yield resumes after input | chunk a long task & stay responsive (modern INP fix) |
async function process(items){
let last = performance.now();
for (const it of items){
work(it);
if (performance.now() - last > 50){ // yield on a ~50ms budget, NOT per item
await scheduler.yield(); // ends task; resumes as a NEW, prioritised task
last = performance.now();
}
}
} // one 300ms task → six ~50ms tasks w/ render+input gaps → tap waits ~50ms not 300ms
| Approach | Task boundary? | Resume where |
|---|---|---|
await scheduler.yield() | yes | front — prioritised, ahead of other tasks |
setTimeout(r,0) | yes | back of queue — can be starved |
await Promise.resolve() | no (microtask) | same turn, before paint — frees nothing |
scheduler.yield() = Chromium today ⇒ setTimeout/postTask fallback.Sources: MDN — Execution model · Jake Archibald — Tasks, microtasks, queues · MDN — Microtask guide · MDN — rAF · web.dev — Optimize long tasks
Promise.then before setTimeout(0).A D C B: sync (A,D) → microtask (C) → macrotask (B).await = microtask, deferred. Not synchronous.setTimeout loop (no paint between). Break the chain with a task.rAF (not setTimeout(16), not microtask). Idle bg → rIC. Long task → scheduler.yield/Worker.scheduler.yield() resumes at the front (prioritised); setTimeout(0) at the back (starvable); await Promise doesn't yield (microtask). None move work off-thread — that's a Worker.