1. Planteamiento
Una página actualiza los resultados de búsqueda a medida que el usuario escribe, mientras analiza un índice sin conexión y precarga la siguiente página. Diseñe una capa de tareas utilizando la Prioritized Task Scheduling API: las actualizaciones visibles deben ejecutarse con prontitud sin que la precarga en segundo plano bloquee la entrada de datos. Explique scheduler.postTask(), scheduler.yield(), las señales de cancelación y el comportamiento cuando la API no está disponible.
2. Restricciones y aclaraciones
- Todas las tareas se ejecutan en el bucle de eventos de una única ventana o Worker; una función síncrona larga sigue bloqueando ese hilo.
- Clasifique el trabajo al menos en prioridades
user-blocking,user-visibleybackground. - El desplazamiento (scrolling), los cambios de ruta o las nuevas entradas pueden hacer que el trabajo quede obsoleto, por lo que debe poder cancelarse o repriorizarse.
- Mantenga una alternativa (fallback) funcional; la corrección del negocio no puede depender del soporte del navegador.
3. Enfoque principal
scheduler.postTask(callback, options) encola una retrollamada (callback) con una prioridad y devuelve una Promise. priority puede ser user-blocking, user-visible o background. Pase un AbortSignal para cancelar; un TaskController compartido también puede cambiar la prioridad de una tarea que no ha comenzado. scheduler.yield() permite que una función asíncrona devuelva voluntariamente el control al navegador antes de continuar.
La prioridad cambia el orden; no realiza una apropiación preventiva (preemption) del JavaScript que ya se está ejecutando. Mantenga cada retrollamada corta y ceda el control (yield) entre bloques. Cuando la API no esté disponible, utilice lotes pequeños de setTimeout o MessageChannel, o el programador de un framework existente, conservando las mismas semánticas de cancelación y resultados obsoletos.
4. Implementación de referencia
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. Rendimiento y corrección
La prioridad no cambia los resultados del programa ni interrumpe una retrollamada en ejecución; solo afecta el orden relativo de las tareas que no han comenzado. La Promise de scheduler.postTask() se resuelve con el valor de retorno de la retrollamada, mientras que los errores de la retrollamada o la cancelación deben ser gestionados como rechazos por quien realiza la llamada.
El verdadero límite de rendimiento es la duración de la tarea y el trabajo total: un bucle síncrono de 200 ms sigue bloqueando la entrada cuando se coloca en una cola de baja prioridad. Mida las Long Tasks, el retraso de entrada (input delay) y la tasa de acierto de cancelaciones por lote, y luego ajuste el tamaño de los bloques. El trabajo pesado para la CPU que pueda ejecutarse en paralelo podría pertenecer a un Worker; la prioridad por sí sola no puede ocultar la inanición del hilo principal.
6. Preguntas de seguimiento y trampas comunes
- Detecte
globalThis.scheduler?.postTasken lugar de adivinar la compatibilidad a partir de la marca o versión del navegador. - Un
AbortSignalcancela el trabajo que no ha comenzado o que observa la señal; no puede detener por la fuerza una retrollamada síncrona que ya se esté ejecutando. - La prioridad dinámica no es apropiación preventiva. Si una tarea ya salió de la cola, utilice una verificación de versión a nivel de negocio para evitar confirmar resultados obsoletos.
- Un fallback basado en
setTimeoutno puede reproducir todas las semánticas de prioridad nativas, por lo que debe verificar explícitamente el trabajo visible, el trabajo en segundo plano y las rutas de cancelación.
7. Lecturas complementarias
Compare scheduler.postTask() con requestIdleCallback(), MessageChannel y los programadores de frameworks: el primero proporciona prioridad y cancelación explícitas, las retrollamadas inactivas dependen de oportunidades de inactividad, los canales de mensajes solo encolan trabajo y los frameworks pueden añadir semánticas de ciclo de vida de componentes. Elija según la compatibilidad, el tipo de tarea y el comportamiento medido.
8. Puntos de evaluación en entrevistas
Capacidad para explicar los límites de prioridad
El candidato debe cubrir las tres prioridades, la Promise devuelta y el hecho de que solo se ordenan las tareas que aún no han comenzado; no es apropiación preventiva de hilos.
Capacidad para diseñar trabajo cancelable
Debe utilizar AbortSignal o TaskController, explicar que una retrollamada iniciada no se puede detener por la fuerza y añadir comprobaciones de versión para resultados obsoletos.
Capacidad para escribir una alternativa progresiva (fallback)
Debe detectar primero las características (feature-detect) y luego proporcionar programación mediante temporizadores, canales de mensajes o frameworks, conservando las clases de tareas y el comportamiento de cancelación.
Capacidad para demostrar el beneficio con datos
Debe medir Long Tasks, el retraso de entrada, la duración de los bloques y la tasa de acierto de cancelaciones, y reconocer que los Workers aíslan el trabajo verdaderamente pesado para la CPU.