1. Prompt
A page updates search results as the user types while parsing an offline index and prefetching the next page. Design a task layer using the Prioritized Task Scheduling API: visible updates should run promptly without background prefetch blocking input. Explain scheduler.postTask(), scheduler.yield(), cancellation signals, and behavior when the API is unavailable.
2. Constraints and clarifications
- All tasks run in one window or Worker event loop; a long synchronous function still blocks that thread.
- Classify work into at least
user-blocking,user-visible, andbackgroundpriorities. - Scrolling, route changes, or new input can make work obsolete, so it must be cancellable or reprioritizable.
- Keep a working fallback; business correctness cannot depend on browser support.
3. Core approach
scheduler.postTask(callback, options) queues a callback with a priority and returns a Promise. priority can be user-blocking, user-visible, or background. Pass an AbortSignal to cancel; a shared TaskController can also change the priority of a task that has not started. scheduler.yield() lets an async function voluntarily return control to the browser before continuing.
Priority changes ordering; it does not preempt JavaScript that is already running. Keep each callback short and yield between chunks. When the API is unavailable, use small setTimeout or MessageChannel batches, or an existing framework scheduler, while preserving the same cancellation and stale-result semantics.
4. Reference implementation
const scheduler = globalThis.scheduler;
function scheduleWork(task, priority, signal) {
if (scheduler?.postTask) {
return scheduler.postTask(task, { priority, signal });
}
return new Promise((resolve, reject) => {
const run = () => {
if (signal?.aborted) {
reject(signal.reason);
return;
}
Promise.resolve().then(task).then(resolve, reject);
};
setTimeout(run, priority === "background" ? 50 : 0);
});
}
async function indexInChunks(items, signal) {
for (let i = 0; i < items.length; i += 100) {
await scheduleWork(() => buildIndex(items.slice(i, i + 100)),
"background", signal);
if (scheduler?.yield && i + 100 < items.length) {
await scheduler.yield({ signal });
}
}
}5. Performance and correctness
Priority does not change program results or interrupt a running callback; it only affects the relative order of tasks that have not started. The Promise from scheduler.postTask() settles with the callback’s return value, while callback errors or cancellation should be handled as rejections by the caller.
The real performance boundary is task duration and total work: a 200 ms synchronous loop still blocks input when placed in a low-priority queue. Measure Long Tasks, input delay, and cancellation hit rate per batch, then tune the chunk size. CPU-heavy work that can run in parallel may belong in a Worker; priority alone cannot hide main-thread starvation.
6. Follow-ups and traps
- Detect
globalThis.scheduler?.postTaskinstead of guessing support from browser brand or version. - An
AbortSignalcancels work that has not started or that observes the signal; it cannot forcibly stop a synchronous callback already running. - Dynamic priority is not preemption. If a task has already left the queue, use a business-level version check to avoid committing stale results.
- A
setTimeoutfallback cannot reproduce all native priority semantics, so verify visible work, background work, and cancellation paths explicitly.
7. Further reading
Compare scheduler.postTask() with requestIdleCallback(), MessageChannel, and framework schedulers: the first provides explicit priority and cancellation, idle callbacks depend on idle opportunities, message channels only queue work, and frameworks may add component-lifecycle semantics. Choose using compatibility, task type, and measured behavior.
8. Interview scoring points
Can explain priority boundaries
The candidate should cover the three priorities, the returned Promise, and the fact that only not-yet-started tasks are ordered; it is not thread preemption.
Can design cancellable work
They should use AbortSignal or TaskController, explain that a started callback cannot be forcibly stopped, and add stale-result version checks.
Can write a progressive fallback
They should feature-detect first, then provide timer, message-channel, or framework scheduling while preserving task classes and cancellation behavior.
Can prove the benefit with data
They should measure Long Tasks, input delay, chunk duration, and cancellation hit rate, and recognize that Workers isolate truly CPU-heavy work.