Tareas en segundo plano en el navegador con la Scheduler API
Tu aplicación se siente rápida hasta que deja de serlo. Un usuario hace clic en un botón y no sucede nada durante 300 milisegundos porque el hilo principal está ocupado procesando algo que no necesitaba ejecutarse en ese momento. Ese es el problema central que la Prioritized Task Scheduling API —comúnmente llamada Scheduler API— está diseñada para resolver.
Este artículo aborda cómo scheduler.postTask() y scheduler.yield() te ofrecen un control práctico sobre la planificación del hilo principal, cuándo usarlos en lugar de enfoques más antiguos, y cómo se presenta actualmente el panorama de soporte en los navegadores.
Puntos clave
- La Scheduler API te da un control detallado sobre cuándo y cómo se ejecuta el trabajo del hilo principal, con tres niveles de prioridad:
user-blocking,user-visibleybackground. scheduler.postTask()aplaza el trabajo con una prioridad explícita, mientras quescheduler.yield()divide tareas largas para que el navegador pueda gestionar la entrada del usuario entre pasos.- Ninguno de los dos métodos saca el trabajo del hilo principal. Para un paralelismo real, usa Web Workers.
- Los navegadores basados en Chromium y Firefox soportan la API; Safari no lo hace, así que incluye siempre un fallback con
setTimeout. - Una planificación más inteligente puede mejorar el Interaction to Next Paint (INP) y la capacidad de respuesta general.
Por qué importa la planificación del hilo principal
JavaScript se ejecuta en un único hilo. Cada ejecución de script, actualización del DOM y manejador de eventos compite por el mismo recurso. Cuando una tarea larga bloquea ese hilo, el navegador no puede responder a la entrada del usuario, lo que afecta directamente tu puntuación de Interaction to Next Paint (INP).
Las soluciones tradicionales tienen sus limitaciones:
setTimeout(fn, 0)aplaza el trabajo pero no te da control sobre la prioridad. Se ejecuta sin importar lo ocupado que esté el hilo.requestIdleCallback()espera a que haya tiempo de inactividad, lo cual es útil para trabajo de baja prioridad, pero no tiene sistema de prioridades, el soporte en Safari es limitado y puede retrasarse indefinidamente bajo carga.
La Scheduler API resuelve ambos problemas.
Cómo funciona scheduler.postTask()
scheduler.postTask() es el punto de entrada principal de la Prioritized Task Scheduling API. Programa un callback para que se ejecute en el hilo principal con un nivel de prioridad específico.
scheduler.postTask(() => {
sendAnalyticsEvent('page_view');
}, { priority: 'background' });
Existen tres niveles de prioridad:
user-blocking— la más alta, para trabajo que bloquea directamente una interacción del usuariouser-visible— el valor predeterminado, para trabajo que afecta lo que el usuario ve pero no está bloqueandobackground— la más baja, para analítica, prefetching o limpieza
Esta es la diferencia clave respecto a APIs anteriores: no solo estás aplazando el trabajo, le estás diciendo al navegador qué tan importante es en relación con todo lo demás en la cola.
scheduler.postTask() devuelve una Promise, lo que facilita su uso con async/await y el manejo limpio de errores:
try {
await scheduler.postTask(() => processLargeDataset(data), {
priority: 'background'
});
} catch (err) {
// Task was aborted or failed
console.warn('Task did not complete:', err);
}
Dividir tareas largas con scheduler.yield()
scheduler.yield() es una incorporación más reciente que resuelve un problema diferente: ¿qué haces cuando ya estás dentro de una tarea larga y necesitas darle al navegador la oportunidad de gestionar la entrada antes de continuar?
async function processItems(items) {
for (const item of items) {
processItem(item);
await scheduler.yield(); // yield back to the browser between items
}
}
Cada await scheduler.yield() crea un punto de control donde el navegador puede gestionar las interacciones de usuario pendientes antes de reanudar tu bucle. Por defecto, la continuación después de yield() se programa con prioridad user-visible, aunque puede heredar la prioridad de una llamada postTask() que la envuelva. Esta es una de las herramientas más prácticas para reducir tareas largas sin reestructurar todo tu código.
Discover how at OpenReplay.com.
Una aclaración importante
Ni scheduler.postTask() ni scheduler.yield() sacan el trabajo del hilo principal. Las tareas programadas de esta manera siguen ejecutándose en el hilo principal; simplemente se ponen en cola y se priorizan de forma más inteligente. Si necesitas una ejecución verdaderamente paralela, para eso están los Web Workers.
Soporte en navegadores
El soporte para la Scheduler API es sólido en navegadores basados en Chromium (Chrome, Edge, Opera). El soporte en Firefox llegó mucho más tarde que en Chromium, y Safari todavía no soporta la API actualmente. Puedes verificar la matriz de soporte actual en Can I Use.
Una comprobación mínima de la característica antes de usarla:
if ('scheduler' in window && 'postTask' in scheduler) {
scheduler.postTask(myTask, { priority: 'background' });
} else {
// Fallback for Safari and older browsers
setTimeout(myTask, 0);
}
Para scheduler.yield() específicamente, comprueba el soporte por separado antes de depender de él en producción, ya que se lanzó más tarde que postTask() y tiene una cobertura más limitada:
async function safeYield() {
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
Cuándo recurrir a la Scheduler API
Usa scheduler.postTask() cuando necesites aplazar trabajo no crítico —analítica, prefetching o procesamiento posterior al renderizado— con control explícito de prioridad. Usa scheduler.yield() cuando tengas un bucle o un proceso de varios pasos que corra el riesgo de bloquear el manejo de la entrada.
Si tus usuarios están principalmente en Chromium o Firefox, la Scheduler API es práctica de usar hoy con detección de características y fallbacks en su lugar. Para una cobertura más amplia, mantén el fallback con setTimeout hasta que Safari se ponga al día.
Conclusión
La Scheduler API cierra una brecha de larga data en cómo la plataforma web gestiona el trabajo del hilo principal. En lugar de depender de herramientas burdas como setTimeout(fn, 0) o el inconsistentemente soportado requestIdleCallback(), ahora puedes expresar intención: esta tarea es crítica, aquella puede esperar, y este bucle debería pausarse para la entrada del usuario. El resultado pueden ser interacciones más fluidas y mejores puntuaciones de INP con relativamente pocos cambios de código. Combínalo con una comprobación de características y un fallback sensato, y podrás adoptarlo de forma segura hoy mismo.
Preguntas frecuentes
No. Todas las tareas programadas con scheduler.postTask() siguen ejecutándose en el hilo principal. La API solo cambia cuándo y en qué orden se ejecutan las tareas asignándoles prioridades. Si necesitas un paralelismo real para trabajo intensivo de CPU, usa Web Workers en su lugar, que ejecutan código en un hilo separado y se comunican con el hilo principal mediante mensajes.
Usa scheduler.yield() dentro de una función o bucle existente cuando quieras pausar brevemente para que el navegador pueda gestionar la entrada del usuario y luego reanudar. Usa scheduler.postTask() cuando quieras programar una unidad de trabajo discreta para ejecutarse más tarde con una prioridad específica. Resuelven problemas relacionados pero distintos: ceder el paso en medio de una tarea versus encolar nuevas tareas.
INP mide la rapidez con la que tu página responde a las interacciones del usuario. Las tareas largas en el hilo principal son la causa más común de un INP deficiente. Al ceder el paso entre pasos y asignar menor prioridad al trabajo no crítico, la Scheduler API ayuda a mantener el hilo principal disponible para gestionar clics, toques y pulsaciones de teclas con prontitud, lo que puede reducir la latencia de interacción.
Sí, siempre y cuando incluyas una comprobación de características y un fallback. Un patrón sencillo es comprobar la existencia de scheduler.postTask y recurrir a setTimeout cuando no esté disponible. Esto mantiene tu código funcionando en Safari y navegadores antiguos, al tiempo que permite a los usuarios de Chromium y Firefox beneficiarse de la planificación priorizada. Existen polyfills, pero generalmente son innecesarios para un uso básico.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.