wandres.dev
EL HILO PRINCIPAL · Long tasks y bloqueo

Qué es una long task y de dónde sale el umbral de 50 milisegundos

La definición exacta de tarea en el modelo de eventos del navegador, por qué el umbral está en 50 y no en otro número, qué cuenta como una sola tarea, y las limitaciones de la API que la mide.

⏱ 18 min

El hilo principal del navegador procesa una cola de tareas, y mientras ejecuta una no puede hacer nada más: ni responder a un clic, ni ejecutar una animación, ni pintar. Una tarea larga es una tarea que ocupa el hilo tanto tiempo que el usuario lo nota. El umbral convencional está en cincuenta milisegundos, y ese número no es arbitrario: sale de una cuenta sobre la percepción humana que conviene conocer, porque explica por qué la métrica está donde está y qué significa realmente superarla.

🎯 Al terminar esta lección sabrás
  • Definir qué es una tarea en el bucle de eventos y qué la delimita.
  • Derivar el umbral de 50 milisegundos a partir del objetivo de respuesta.
  • Enumerar qué operaciones se agrupan en una sola tarea y cuáles no.
  • Reconocer las tres limitaciones de la API de tareas largas.

Qué es una tarea

El navegador tiene un bucle de eventos que saca elementos de una cola y los ejecuta hasta el final. Cada elemento es una tarea, y las tareas no se pueden interrumpir: una vez que empieza a ejecutarse, el hilo es suyo hasta que devuelve el control.

Lo que genera tareas:

  • La evaluación de un script.
  • La ejecución de un manejador de eventos, con todos los oyentes registrados para ese evento.
  • Una devolución de llamada de setTimeout o setInterval.
  • El procesamiento de una respuesta de red que resuelve una promesa desde una tarea.
  • El análisis de una porción del HTML durante la carga.
  • La ejecución de una devolución de llamada de un observador.

Y lo que no genera una tarea nueva, sino que se ejecuta al final de la actual: las microtareas. Las continuaciones de promesas, queueMicrotask y los observadores de mutación se vacían al terminar la tarea en curso, antes de devolver el control al bucle de eventos. Esto es lo que hace que await no ceda el hilo, un punto que confunde a mucha gente y que es central en el nivel siguiente.

// Todo esto es UNA sola tarea. Los await no ceden nada al navegador.
button.addEventListener('click', async () => {
  const datos = await Promise.resolve(calcularPesado());   // microtarea
  const otro  = await Promise.resolve(procesar(datos));    // microtarea
  pintar(otro);
});

Si calcularPesado tarda 300 milisegundos, la tarea dura 300 milisegundos, y los dos await no han ayudado en absoluto. Ceder de verdad requiere programar una tarea nueva, que es el tema de ceder el hilo.

De dónde sale el 50

La cuenta es esta y conviene tenerla, porque es la justificación de toda la métrica.

El objetivo de respuesta a una interacción está en 100 milisegundos: por debajo de eso, el usuario percibe la respuesta como inmediata y atribuye el resultado a su propia acción. Por encima, empieza a percibir latencia.

Ahora imagina que el usuario hace clic en el peor momento posible: justo después de que haya empezado una tarea. El navegador no puede interrumpirla, así que el manejador del clic esperará a que termine. Si la tarea dura T, el retraso de entrada puede llegar a T.

Para que el navegador tenga margen de responder en 100 milisegundos, necesita que la tarea que estaba corriendo termine pronto y que además quede tiempo para ejecutar el manejador y pintar. Repartiendo ese presupuesto a partes aproximadamente iguales entre la espera y el trabajo de respuesta, salen 50 milisegundos para la tarea bloqueante y 50 para responder.

De ahí el umbral. Una tarea de 50 milisegundos es, en el peor caso, 50 milisegundos de retraso de entrada, que todavía deja margen. Una de 300 son 300 milisegundos de retraso, y ahí ya se ha perdido la sensación de inmediatez antes de haber ejecutado una sola línea del manejador.

Esta derivación explica también por qué el umbral es el mismo en todos los dispositivos, aunque el trabajo que cabe en 50 milisegundos sea muy distinto en un portátil y en un móvil barato: el umbral describe la percepción humana, no la capacidad de la máquina. Lo que cambia entre dispositivos es cuánto código cabe dentro, no cuánto tiempo tolera el usuario.

Qué se agrupa en una sola tarea

Este punto produce sorpresas al leer perfiles, porque el agrupamiento no siempre coincide con la intuición.

Todos los oyentes de un mismo evento son una sola tarea. Si tienes cinco oyentes de click en la cadena de propagación y cada uno tarda 30 milisegundos, la tarea dura 150. En el perfil aparece como una única tarea larga, y hay que abrirla para ver que son cinco.

El pintado no es parte de la tarea, pero espera a ella. El renderizado ocurre entre tareas, en el momento en que el navegador decide actualizar el fotograma. Una tarea larga retrasa el pintado aunque no lo incluya.

Una devolución de llamada de requestAnimationFrame se ejecuta como parte del ciclo de renderizado, no como una tarea normal, y aun así su duración cuenta para el bloqueo. Un rAF que tarda 80 milisegundos bloquea igual.

Las respuestas de red no generan trabajo por sí solas. Descargar 2 MB no bloquea el hilo. Lo que bloquea es lo que haces con ellos: parsear el JSON, construir objetos, actualizar el DOM. Un JSON.parse de un documento de 3 MB puede costar 200 milisegundos y aparece como una tarea larga sin ninguna función tuya visible en la pila.

Las tres limitaciones de la API

La API de tareas largas informa de las tareas que superan los 50 milisegundos. Es útil y tiene tres carencias serias que hay que conocer para no sacar conclusiones equivocadas.

Limitación uno: la atribución es pobre. La entrada te dice cuánto duró la tarea y de qué contenedor procede —el documento principal, un <iframe>, un <embed>— pero no te dice qué función la causó. Con containerSrc puedes identificar un tercero incrustado; dentro de tu propio documento, la entrada te dice que hubo 340 milisegundos de bloqueo y nada más.

Limitación dos: no ve por debajo del umbral. Cinco tareas de 45 milisegundos seguidas son 225 milisegundos de hilo ocupado y cero tareas largas reportadas. Este patrón es muy común en aplicaciones que ya han intentado trocear y lo han hecho a medias, y produce la situación desconcertante de una página que se siente lenta con una métrica de tareas largas impecable.

Limitación tres: no incluye el tiempo de renderizado. El trabajo de estilo, layout y pintura que ocurre después de la tarea no cuenta como tarea larga aunque bloquee el hilo igual.

Las tres limitaciones son las que motivaron una API posterior, la de fotogramas de animación largos, que informa por fotograma en lugar de por tarea, incluye el renderizado y da atribución de guiones con nombre de fichero, función y posición. Está disponible en Chromium desde 2024 y es hoy la herramienta correcta para diagnosticar; la API de tareas largas se mantiene por compatibilidad y por su cobertura en más navegadores. Verás las dos en detalle en encontrar las tareas largas.

Una tarea de 300 ms no cuesta 300 ms al usuario: cuesta entre 0 y 300, y ese promedio es lo que hace la métrica engañosa

La derivación del umbral asume el peor caso: que el usuario interactúa justo al empezar la tarea. En la práctica, el momento del clic respecto a la tarea es aleatorio, y eso tiene una consecuencia estadística que cambia cómo hay que priorizar.

Si una tarea dura T y el usuario hace clic en un instante uniformemente distribuido dentro de ella, el retraso esperado es T/2. Pero la probabilidad de que el clic caiga dentro de la tarea también depende de T: cuanto más larga, más probable. Combinando ambas, el retraso esperado por unidad de tiempo escala con . En cristiano: una tarea de 400 milisegundos no hace el doble de daño que una de 200, hace cuatro veces más.

De ahí sale la regla de priorización que contradice la intuición de repartir el esfuerzo: ataca primero la tarea más larga, no el conjunto de tareas medianas, aunque las medianas sumen más milisegundos en total. Diez tareas de 80 milisegundos suman 800 y hacen menos daño percibido que dos de 400, que suman 800 también.

Y hay un corolario práctico sobre dónde partir el trabajo. Cuando trocees una tarea de 400 milisegundos, no hace falta llegar a trozos de 5: bajar de 400 a cuatro de 100 elimina la mayor parte del daño, y bajar de 100 a veinte de 20 aporta mucho menos por el esfuerzo que cuesta. El rendimiento decreciente empieza pronto. La zona objetivo razonable está en tareas por debajo de 50 milisegundos siempre que sea barato, y por debajo de 100 cuando trocear más complique el código.

El segundo corolario es sobre cuándo ocurren las tareas, que importa tanto como cuánto duran. Una tarea de 500 milisegundos durante el arranque, mientras el usuario todavía está leyendo la cabecera, hace mucho menos daño que una de 150 justo después de que el usuario haya pulsado un botón. La misma cifra en el perfil, experiencias completamente distintas. Por eso el tiempo total de bloqueo, que solo mide la fase de carga, y el INP, que mide la interacción, son métricas complementarias y no sustitutivas: la primera cuenta milisegundos, la segunda cuenta milisegundos en el momento en que duelen.

Lo que hay que llevarse

Tres ideas que sostienen todo el nivel.

El hilo principal es un recurso serializado. No hay concurrencia. Cada milisegundo que consume una tarea es un milisegundo que no está disponible para responder al usuario.

El umbral de 50 describe la percepción, no la máquina. Por eso no se relaja en dispositivos lentos: lo que hay que relajar es la cantidad de trabajo.

La métrica de tareas largas es un detector, no un diagnóstico. Te dice que hay un problema y aproximadamente dónde; identificar la causa exige el perfilador o la API de fotogramas largos.

⚔️ Reto práctico

Instrumenta tu página con un observador de tareas largas y navega por ella durante un minuto en el dispositivo de referencia. Anota la duración de cada tarea reportada y calcula dos cifras: la suma total y el cuadrado de la más larga dividido entre el cuadrado de la mediana. Si esa segunda cifra es mayor que diez, tienes un problema de cola y la prioridad está clara: la tarea más larga, no las demás.