Depuración de JavaScript asíncrono con las Herramientas para desarrolladores de Chrome

Introducción

Una función potente que hace único a JavaScript es su capacidad de trabajar de forma asíncrona a través de funciones de devolución de llamada. Asignar devoluciones de llamada asíncronas te permite escribir código controlado por eventos, pero también hace que el seguimiento de errores sea una experiencia de peluquería, ya que JavaScript no se ejecuta de forma lineal.

Afortunadamente, ahora en las Herramientas para desarrolladores de Chrome, puedes ver la pila de llamadas completa de las devoluciones de llamada asíncronas de JavaScript.

Una breve descripción general de las pilas de llamadas asíncronas.
Una descripción general de avance rápido de las pilas de llamadas asíncronas. (Pronto desglosaremos el flujo de esta demostración).

Una vez que habilites la función de pila de llamadas asíncronas en Herramientas para desarrolladores, podrás desglosar el estado de tu aplicación web en varios momentos. Recorre el seguimiento de pila completa de algunos objetos de escucha de eventos, setInterval, setTimeout, XMLHttpRequest, promesas, requestAnimationFrame, MutationObservers y muchos más.

A medida que recorres el seguimiento de pila, también puedes analizar el valor de cualquier variable en ese punto particular de ejecución del tiempo de ejecución. Es como una máquina del tiempo para las expresiones del reloj.

Habilitemos esta función y veamos algunas de estas situaciones.

Cómo habilitar la depuración asíncrona en Chrome

Para probar esta nueva función, habilítala en Chrome. Ve al panel Fuentes de las Herramientas para desarrolladores de Chrome Canary.

Junto al panel Call Stack en el lado derecho, hay una nueva casilla de verificación para “Async”. Activa o desactiva la casilla de verificación para activar o desactivar la depuración asíncrona (aunque una vez que esté activada, es posible que no quieras desactivarla).

Activa o desactiva la función asíncrona.

Capturar eventos de temporizador retrasados y respuestas XHR

Es probable que ya lo hayas visto en Gmail:

Gmail está intentando enviar un correo electrónico.

Si hay un problema para enviar la solicitud (ya sea que el servidor tenga problemas o haya problemas de conectividad de red en el lado del cliente), Gmail intentará volver a enviar el mensaje automáticamente después de un tiempo de espera breve.

Para ver cómo las pilas de llamadas asíncronas pueden ayudarnos a analizar eventos de temporizador retrasados y respuestas XHR, recreamos ese flujo con un ejemplo de Gmail de prueba. Puedes encontrar el código JavaScript completo en el vínculo anterior, pero el flujo es el siguiente:

Diagrama de flujo de un ejemplo de Gmail de prueba.
En el diagrama anterior, los métodos destacados en azul son los protagonistas para que esta nueva función de las Herramientas para desarrolladores sea la más beneficiosa, ya que estos métodos funcionan de forma asíncrona.

Si solo observas el panel Call Stack en versiones anteriores de Herramientas para desarrolladores, una interrupción dentro de postOnFail() te brindaba poca información sobre el origen desde el que se llamaba a postOnFail(). Sin embargo, observa la diferencia cuando actives las pilas asíncronas:

Antes
Se estableció un punto de interrupción en un ejemplo de Gmail de prueba sin pilas de llamadas asíncronas.
El panel Pila de llamadas sin el modo asíncrono habilitado

Aquí puedes ver que postOnFail() se inició a partir de una devolución de llamada de AJAX, pero no hay más información.

Después
Se estableció un punto de interrupción en un ejemplo de Gmail de prueba con pilas de llamadas asíncronas.
El panel Pila de llamadas con el modo asíncrono habilitado

Aquí puedes ver que la XHR se inició desde submitHandler(). ¡Genial!

Con las pilas de llamadas asíncronas activadas, puedes ver toda la pila de llamadas para ver con facilidad si la solicitud se inició desde submitHandler() (lo que sucede después de hacer clic en el botón de envío) o desde retrySubmit() (lo que sucede después de un retraso de setTimeout()):

submitHandler()
Se estableció un punto de interrupción en un ejemplo de Gmail de prueba con pilas de llamadas asíncronas.
retrySubmit()
Otro punto de interrupción establecido en un ejemplo de Gmail de prueba con pilas de llamadas asíncronas

Observa expresiones de forma asíncrona

Cuando recorres toda la pila de llamadas, las expresiones observadas también se actualizan para reflejar el estado en el que se encontraban en ese momento.

Ejemplo del uso de expresiones watch con pilas de llamadas aysnc

Evalúa el código a partir de permisos anteriores

Además de simplemente observar expresiones, puedes interactuar con el código desde alcances anteriores en el panel de la consola de JavaScript de Herramientas para desarrolladores.

Imagina que eres el Dr. Who y necesitas ayuda para comparar el reloj de antes de llegar a Tardis con el “ahora”. Desde la consola de Herramientas para desarrolladores, puedes evaluar, almacenar y realizar cálculos fácilmente en valores desde diferentes puntos de ejecución.

Ejemplo del uso de la Consola de JavaScript con pilas de llamadas de aysnc.
Usa la consola de JavaScript junto con las pilas de llamadas asíncronas para depurar tu código. Puedes encontrar la demostración anterior aquí.

Permanecer dentro de Herramientas para desarrolladores para manipular tus expresiones te ahorrará tiempo de tener que volver a tu código fuente, hacer modificaciones y actualizar el navegador.

Devela resoluciones de promesas encadenadas

Si creyeras que el flujo simulado de Gmail anterior era difícil de resolver sin la función de pila de llamadas asíncronas habilitada, ¿puedes imaginarte cuánto sería con los flujos asíncronos más complejos, como las promesas encadenadas? Repasemos el ejemplo final del instructivo de Jake Archibald sobre promesas de JavaScript.

A continuación, se muestra una pequeña animación del recorrido de las pilas de llamadas en el ejemplo async-best-example.html de Jake.

Antes
Se estableció un punto de interrupción en el ejemplo de promesas sin pilas de llamadas asíncronas
El panel Pila de llamadas sin el modo asíncrono habilitado

Observa que el panel Pila de llamadas tiene poca información a la hora de depurar promesas.

Después
Punto de interrupción establecido en el ejemplo de promesas con pilas de llamadas asíncronas.
El panel Pila de llamadas con el modo asíncrono habilitado

¡Bien! ¡Y esas promesas! Muchas devoluciones de llamada.

Obtén estadísticas sobre tus animaciones web

Profundicemos en los archivos de HTML5Rocks. ¿Recuerdas Leaner, Meaner, Faster Animations with requestAnimationFrame de Paul Lewis?

Abre la demostración de requestAnimationFrame y agrega un punto de interrupción al comienzo del método update() (cerca de la línea 874) de post.html. Con las pilas de llamadas asíncronas, obtenemos muchas más estadísticas sobre requestAnimationFrame, incluida la capacidad de caminar hasta la devolución de llamada del evento de desplazamiento iniciador.

Antes
Se estableció un punto de interrupción en el ejemplo de requestAnimationFrame sin pilas de llamadas asíncronas.
El panel Pila de llamadas sin el modo asíncrono habilitado
Después
Se estableció un punto de interrupción en el ejemplo de requestAnimationFrame con pilas de llamadas asíncronas.
Y con el modo asíncrono habilitado

Cómo hacer un seguimiento de las actualizaciones del DOM cuando se usa MutationObserver

MutationObserver nos permite observar cambios en el DOM. En este ejemplo simple, cuando haces clic en el botón, se agrega un nuevo nodo del DOM a <div class="rows"></div>.

Se agregó un punto de interrupción en nodeAdded() (línea 31) en demo.html Con las pilas de llamadas asíncronas habilitadas, ahora puedes hacer que la pila de llamadas pase por addNode() hasta el evento de clic inicial.

Antes
Se estableció un punto de interrupción en el ejemplo de ImageObserver sin pilas de llamadas asíncronas.
El panel Pila de llamadas sin el modo asíncrono habilitado
Después
Se estableció un punto de interrupción en el ejemplo de rotateObserver con pilas de llamadas asíncronas.
Y con el modo asíncrono habilitado

Sugerencias para depurar JavaScript en pilas de llamadas asíncronas

Nombra tus funciones

Si sueles asignar todas las devoluciones de llamada como funciones anónimas, tal vez quieras asignarles un nombre para facilitar la visualización de la pila de llamadas.

Por ejemplo, tomemos una función anónima como la siguiente:

window.addEventListener('load', function() {
  // do something
});

Y dale un nombre como windowLoaded():

window.addEventListener('load', function <strong>windowLoaded</strong>(){
  // do something
});

Cuando se activa el evento de carga, este aparecerá en el seguimiento de pila de Herramientas para desarrolladores con su nombre de función en lugar de la críptica "(función anónima)". Esto hace que sea mucho más fácil ver a simple vista lo que sucede en el seguimiento de pila.

Antes
Una función anónima.
Después
Una función con nombre

Explora más

En resumen, estas son todas las devoluciones de llamada asíncronas en las que Herramientas para desarrolladores mostrará la pila de llamadas completa:

  • Temporizadores: Vuelve a donde se inicializó setTimeout() o setInterval().
  • XHR: Vuelve a donde se llamó a xhr.send().
  • Marcos de animación: Vuelve a donde se llamó a requestAnimationFrame.
  • Promesas: Regresa a donde se resolvió una promesa.
  • Object.observe: regresa a la ubicación original de la devolución de llamada del observador.
  • MutationObservers: Vuelve al lugar donde se activó el evento del observador de mutaciones.
  • window.postMessage(): Revisar las llamadas de mensajería dentro del proceso.
  • DataTransferItem.getAsString()
  • API de FileSystem
  • IndexedDB
  • WebSQL
  • Eventos del DOM aptos a través de addEventListener(): Vuelve a donde se activó el evento. Por motivos de rendimiento, no todos los eventos del DOM son aptos para la función de las pilas de llamadas asíncronas. Algunos ejemplos de eventos disponibles actualmente son "scroll", "hashchange" y "selectionchange".
  • Eventos multimedia a través de addEventListener(): Camina hasta donde se activó el evento. Entre los eventos multimedia disponibles, se incluyen los siguientes: eventos de audio y video (p.ej., "reproducir", "pausar", "cambiar la tarifa"), eventos de WebRTC MediaStreamTrackList (p.ej., "addtrack" y "removetrack") y eventos de MediaSource (p.ej., "sourceopen").

Poder ver el seguimiento de pila completa de tus devoluciones de llamada de JavaScript debería mantener esos pelos en la cabeza. Esta función de Herramientas para desarrolladores será especialmente útil cuando ocurren varios eventos asíncronos entre sí o si se arroja una excepción no detectada desde una devolución de llamada asíncrona.

Pruébalo en Chrome. Si tienes comentarios sobre esta nueva función, escríbenos en la herramienta de seguimiento de errores de las Herramientas para desarrolladores de Chrome o en el grupo de Herramientas para desarrolladores de Chrome.