Long Tasks API de W3C para identificar tareas de larga ejecución
Índice
Las tareas de larga ejecución plantean grandes inconvenientes de rendimiento, y tienen como origen ejecuciones de JavaScript que no son del todo óptimas. Estas tareas implican el bloqueo temporal del hilo principal, que es el encargado de procesar el trabajo requerido por JavaScript -siempre que no se utilicen web workers-, pero también de procesar las acciones de interacción de los usuarios.
En esta publicación se abordarán introducciones resumidas a los aspectos relacionados con las tareas de larga ejecución, y también el modo en que podemos analizar en qué lugares y momentos de un mismo entorno web están sucediendo.
¿Qué es una tarea, y cuándo se considera de larga ejecución?
Una tarea en el ámbito de navegación, es toda acción que lleva a cabo el navegador ya sea por imposición (código o instrucciones ofrecidos al usuario) o por demanda del mismo (eventos de click, scroll, o cualquier otro tipo).
Algunos ejemplos de tareas que se llevan a cabo mediante el hilo principal, pueden ser las siguientes:
- Renderizado de la interfaz de usuario: Lectura e interpretación del código HTML y CSS para su posterior renderización en pantalla.
- Solicitudes y respuestas de red: Descarga de recursos (imágenes, estilos, scripts o cualquier otro tipo) ofrecidas al usuario o demandadas por el mismo.
- Actualización del DOM: Procesamiento de cambios en el HTML.
- Ejecución de JavaScript: Ejecución de código JavaScript obligatorio o demandado por el usuario mediante interacción.
Las tareas se consideran de larga ejecución cuando se prolongan durante más de 50 milisegundos.
¿Cuál es el hilo principal?
Como introducía anteriormente, el hilo principal es el encargado de abordar la mayoría de las tareas implícitas en la navegación, y es muy importante que se encuentre lo más disponible posible para atender las peticiones del usuario, al mismo tiempo que para atender las instrucciones que recibe desde el propio servidor.
Un modo muy sencillo de entender qué es exactamente el hilo principal y por qué es importante no saturarlo, lo podemos encontrar haciendo una aproximación a otros ámbitos:
Imagina que el hilo principal es un cocinero en un restaurante y que debe atender múltiples tareas: cortar verduras, atender los fogones, mezclar ingredientes y también estar atento a las comandas que le trasladan los camareros. Si el cocinero debe cortar demasiadas verduras y hacer demasiadas elaboraciones (renderizado, procesamiento de código, etc.) se corre el riesgo de que no puedan atenderse a tiempo las comandas (peticiones de los usuarios), y los clientes queden insatisfechos por un retraso en su pedido (mala experiencia de navegación).
Por tanto, es muy importante tratar de no saturar al cocinero (hilo principal) con tareas demasiado costosas (de larga ejecución) que le impidan manejar otras simultáneamente, y en lugar de ello, se le deben asignar tareas que le permitan tener mayor disponibilidad (inferiores a 50ms).
Hilo principal y tareas de larga ejecución
Teniendo claros los conceptos de hilo principal y tareas, podemos profundizar en el modo en que estas se procesan por el hilo principal en más detalle.
La siguiente imagen representa la ejecución de una tarea larga (before) y cómo debería ejecutarse realmente para ser considerada óptima (after):
En el primer caso estamos ante una tarea excesivamente larga, durante la cual el hilo principal se encuentra bloqueado y no puede atender ninguna otra tarea, incluidas las interacciones del usuario.
En el segundo caso se representa exactamente la misma tarea, con la excepción de que ha sido dividida en diferentes bloques generando espacios temporales entre las ejecuciones, lo que permite al hilo principal procesar tareas que ocurran entre ellas.
En el siguiente gráfico (before) puede interpretarse visualmente la latencia o tiempo que pasa desde que el usuario realiza una acción (input received) hasta que es procesada (event handler).
Por otra parte, al dividir la ejecución de la tarea en diferentes bloques (idealmente de ejecución inferior a 50ms), se puede observar que el evento o interacción es procesado con mayor rapidez de modo en que puede atenderse la petición y, posteriormente, continuar con la ejecución inicial.
Monitorización con la API de Long Tasks
Long Tasks es una API desarrollada por el Web Performance Working Group de W3C con el propósito de identificar tareas de larga ejecución que bloquean el hilo principal y afectan negativamente al rendimiento. Aunque a continuación mostraré una aproximación a un entorno real, te invito a conocer más detalles sobre ella en su documentación o GitHub.
¿En qué consiste exactamente?
La mayoría de navegadores modernos ofrecen una interfaz de programación de aplicaciones (API) de rendimiento, permitiendo a los desarrolladores obtener información detallada sobre el rendimiento de sus aplicaciones y evaluar las consecuencias o implicaciones de sus desarrollos mediante el intercambio de información gracias a estas API, que pueden ser consultadas mediante JavaScript.
Long Tasks utiliza la API de PerformanceObserver para tal y como su nombre indica, observar; en este caso el comportamiento del hilo principal.
Ejemplo teórico de uso básico
En el siguiente ejemplo se declara un observador que identificará tareas de larga ejecución que se hayan registrado de manera previa, o posterior a la declaración del mismo.
NOTA: Al hacer referencia al registro de tareas de larga ejecución previa a la declaración del observador, no se hace referencia a la posibilidad de recuperar un histórico o información anterior a la navegación actual, si no que simplemente la API de Performance viene implícita en la mayoría de navegadores, y aunque el observador se haya declarado en un momento posterior al manifiesto de tareas de larga ejecución, la información de ellas seguirá quedando registrada, por lo que podrán ser recuperadas con posterioridad durante la misma estancia en la URL observada. Esto permite, por ejemplo, que el observador se declare de manera asíncrona o diferida sin sacrificar eficacia en la medición.
// Se declara el observador de rendimiento (PerformanceObserver) window._observer = new PerformanceObserver(function(entryList) { var entries = entryList.getEntries(); for (var i = 0; i < entries.length; i++) { // Por cada una de las entradas al vector se ejecutará este bucle } }); // Se registra el observador para notificaciones de tareas de larga ejecución (longtask) // Mediante "buffered: true", también se capturan las tareas previas a este registro window._observer.observe({entryTypes: ["longtask"]});
Ejemplo práctico visual
Accede a este ejemplo en vivo de API Long Task en el cual se generan tareas de larga ejecución voluntaria y aleatoriamente para comprobar el funcionamiento de la API. En él, puedes encontrar que cada una de las tareas de larga ejecución puede venir acompañada del momento (ms) en el cual se originó, así como el tiempo durante el cual se ejecutó y mantuvo bloqueado el hilo principal. La línea verde se representa visualmente el tiempo de dedicación del hilo principal a las tareas asumidas.
La impresión en pantalla se muestra porque cada una de las tareas de larga ejecución pasa a la lista (vector) el cual es constantemente iterado para ejecutar una acción determinada con cada uno de los elementos.
Como es evidente, la impresión en pantalla no tiene ninguna utilidad e incluso es un formato poco práctico incluso para entornos de prueba, pero el código también puede adaptarse para que esta información simplemente se reporte en tiempo real a otros paneles de medición, como por ejemplo Google Analytics.
Ejemplo teórico con reporte a Google Analytics
En el siguiente ejemplo simplemente se añade un envío de evento al código que se ejecuta por cada una de las tareas de larga ejecución identificadas, de manera en que pueda obtenerse una perspectiva global del rendimiento del hilo principal en las diferentes páginas que componen el sitio, entendiendo que en cada una de ellas el hilo principal puede tener un comportamiento diferente, al recibir instrucciones diferentes.
window._observer = new PerformanceObserver(function(entryList) { var entries = entryList.getEntries(); for (var i = 0; i < entries.length; i++) { if (entries[i].entryType === "longtask") { // Captura de la URL actual var urlActual = window.location.href; // Envío de evento a Google Analytics gtag('event', 'Tarea de larga ejecución', {'event_category': 'Rendimiento', 'event_label': newItem, 'event_url': urlActual}); } } }); window._observer.observe({entryTypes: ["longtask"]});
De este modo podrá capturarse en tiempo real información de aquellas URLs donde existen problemas de este tipo para abordarlos individualmente, y mantener una monitorización constante de los mismos.