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

Introducción

Una función potente que hace que JavaScript sea único es su capacidad para 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 orientado a eventos, pero también hace que el seguimiento de errores sea una experiencia frustrante, ya que JavaScript no se ejecuta de forma lineal.

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

Descripción general breve de las pilas de llamadas asíncronas.
Una breve descripción general 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 DevTools, podrás analizar el estado de tu app web en varios momentos. Explora el seguimiento de pila completo de algunos objetos de escucha de eventos, setInterval, setTimeout, XMLHttpRequest, promesas, requestAnimationFrame, MutationObservers y mucho más.

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

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

Habilita la depuración asíncrona en Chrome

Para probar esta nueva función, habilítala en Chrome. Ve al panel Sources 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.

Captura eventos de temporizador retrasados y respuestas XHR

Es probable que ya hayas visto esto en Gmail:

Gmail vuelve a intentar enviar un correo electrónico.

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

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

Diagrama de flujo de ejemplo simulado de Gmail.
En el diagrama anterior, los métodos destacados en azul son los lugares ideales para que esta nueva función de DevTools sea la más beneficiosa, ya que estos métodos funcionan de forma asíncrona.

Si solo miras el panel de pila de llamadas en versiones anteriores de DevTools, un punto de interrupción dentro de postOnFail() te brindaría poca información sobre desde dónde se llamaba a postOnFail(). Sin embargo, observa la diferencia cuando se activan las pilas asíncronas:

Antes
Punto de interrupción establecido en el ejemplo simulado de Gmail sin pilas de llamadas asíncronas.
El panel de pila de llamadas sin la función asíncrona habilitada.

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

Después
Punto de interrupción establecido en un ejemplo simulado de Gmail con pilas de llamadas asíncronas.
El panel de pila de llamadas con la operación asíncrona habilitada.

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

Con las pilas de llamadas asíncronas activadas, puedes ver toda la pila de llamadas para ver fácilmente 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 una demora de setTimeout()):

submitHandler()
Punto de interrupción establecido en un ejemplo simulado de Gmail con pilas de llamadas asíncronas
retrySubmit()
Otro punto de interrupción establecido en el ejemplo simulado de Gmail con pilas de llamadas asíncronas

Cómo supervisar expresiones de forma asíncrona

Cuando recorras la pila de llamadas completa, tus expresiones observadas también se actualizarán para reflejar el estado en el que se encontraban en ese momento.

Ejemplo del uso de expresiones de vigilancia con pilas de llamadas asíncronas

Evalúa el código de alcances anteriores

Además de observar expresiones, puedes interactuar con tu código desde alcances anteriores directamente en el panel de la consola de JavaScript de DevTools.

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

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

Si te quedas en DevTools para manipular tus expresiones, ahorrarás tiempo, ya que no tendrás que volver a tu código fuente, hacer ediciones ni actualizar el navegador.

Desenreda las resoluciones de promesas encadenadas

Si pensaste que el flujo simulado de Gmail anterior era difícil de desentrañar sin la función de pila de llamadas asíncrona habilitada, ¿puedes imaginar lo mucho más difícil que sería con flujos asíncronos más complejos, como promesas encadenadas? Repasemos el ejemplo final del instructivo de Jake Archibald sobre promesas de JavaScript.

Esta es una pequeña animación de cómo recorrer las pilas de llamadas en el ejemplo async-best-example.html de Jake.

Antes
Punto de interrupción establecido en el ejemplo de promesas sin pilas de llamadas asíncronas
El panel de pila de llamadas sin la función asíncrona habilitada.

Observa cómo el panel de pila de llamadas tiene muy poca información cuando se intenta depurar promesas.

Después
Punto de interrupción establecido en el ejemplo de promesas con pilas de llamadas asíncronas.
El panel de pila de llamadas con la operación asíncrona habilitada.

¡Vaya! Tales promesas. Demasiadas devoluciones de llamadas.

Obtén estadísticas sobre tus animaciones web

Analicemos en más detalle los archivos de HTML5Rocks. ¿Recuerdas Animaciones más ágiles, más rápidas y más eficientes con requestAnimationFrame de Paul Lewis?

Abre la demo de requestAnimationFrame y agrega un punto de interrupción al comienzo del método update() (alrededor 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 recorrer todo el camino hasta la devolución de llamada del evento de desplazamiento inicial.

Antes
Punto de interrupción establecido en el ejemplo de requestAnimationFrame sin pilas de llamadas asíncronas.
El panel de pila de llamadas sin la función asíncrona habilitada.
Después
Punto de interrupción establecido en el ejemplo de requestAnimationFrame con pilas de llamadas asíncronas
Y con async habilitado.

Hacer un seguimiento de las actualizaciones del DOM cuando se usa MutationObserver

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

Agrega un punto de interrupción dentro de nodeAdded() (línea 31) en demo.html. Con las pilas de llamadas asíncronas habilitadas, ahora puedes recorrer la pila de llamadas a través de addNode() hasta el evento de clic inicial.

Antes
Punto de interrupción establecido en el ejemplo de mutationObserver sin pilas de llamadas asíncronas.
El panel de pila de llamadas sin la función asíncrona habilitada.
Después
Punto de interrupción establecido en el ejemplo de mutationObserver con pilas de llamadas asíncronas.
Y con async habilitado.

Sugerencias para depurar JavaScript en pilas de llamadas asincrónicas

Asigna un nombre a tus funciones

Si tiendes a asignar todas tus devoluciones de llamada como funciones anónimas, te recomendamos que les asignes un nombre para facilitar la visualización de la pila de llamadas.

Por ejemplo, toma una función anónima como esta:

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

Y asígnale un nombre como windowLoaded():

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

Cuando se active el evento de carga, aparecerá en el seguimiento de pila de DevTools con su nombre de función en lugar de la críptica "(anonymous function)". Esto facilita ver de un vistazo 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 DevTools mostrará la pila de llamadas completa:

  • Temporizadores: Regresa al lugar donde se inicializó setTimeout() o setInterval().
  • XHR: Regresa al lugar al que se llamó a xhr.send().
  • Fotogramas de animación: Regresa al lugar al que se llamó a requestAnimationFrame.
  • Promesas: Regresa al punto en el que se resolvió una promesa.
  • Object.observe: Regresa al lugar donde se vinculó originalmente la devolución de llamada del observador.
  • MutationObservers: Regresa al lugar donde se activó el evento del observador de mutaciones.
  • window.postMessage(): Explora las llamadas de mensajería intraproceso.
  • DataTransferItem.getAsString()
  • API de FileSystem
  • IndexedDB
  • WebSQL
  • Eventos del DOM aptos a través de addEventListener(): Regresa al lugar donde se activó el evento. Por motivos de rendimiento, no todos los eventos del DOM son aptos para la función de pilas de llamadas asíncronas. Algunos ejemplos de eventos disponibles actualmente son "scroll", "hashchange" y "selectionchange".
  • Eventos multimedia a través de addEventListener(): Regresa al lugar donde se activó el evento. Los eventos multimedia disponibles incluyen eventos de audio y video (p.ej., "play", "pause", "ratechange"), eventos de WebRTC MediaStreamTrackList (p.ej., "addtrack", "removetrack") y eventos de MediaSource (p.ej., "sourceopen").

Poder ver el seguimiento de pila completo de tus devoluciones de llamada de JavaScript debería mantenerte tranquilo. Esta función de DevTools será especialmente útil cuando ocurran varios eventos asíncronos en relación entre sí, o si se genera una excepción no detectada desde una devolución de llamada asíncrona.

Pruébala en Chrome. Si tienes comentarios sobre esta nueva función, escríbenos en el registro de errores de Chrome DevTools o en el grupo de Chrome DevTools.