Déboguer le code JavaScript asynchrone avec les outils pour les développeurs Chrome

Pearl Chen

Introduction

Une fonctionnalité puissante qui rend JavaScript unique est sa capacité à fonctionner de manière asynchrone via des fonctions de rappel. L'attribution de rappels asynchrones vous permet d'écrire du code basé sur des événements, mais elle facilite également le suivi des bugs, car le JavaScript ne s'exécute pas de manière linéaire.

Heureusement, dans les outils pour les développeurs Chrome, vous pouvez désormais afficher la pile d'appels complète des rappels JavaScript asynchrones.

Présentation rapide des piles d'appels asynchrones
Présentation rapide des piles d'appels asynchrones. (Nous expliquerons bientôt le déroulement de cette démonstration.)

Une fois que vous aurez activé la fonctionnalité de pile d'appel asynchrone dans les outils de développement, vous pourrez voir l'état de votre application Web à différents moments. Parcourir la trace complète de la pile pour certains écouteurs d'événements setInterval, setTimeout, XMLHttpRequest, promesses, requestAnimationFrame, MutationObservers, etc.

Lorsque vous parcourez la trace de la pile, vous pouvez également analyser la valeur de n'importe quelle variable à ce moment précis de l'exécution de l'environnement d'exécution. C'est comme une machine à remonter le temps pour les expressions de votre montre !

Activons cette fonctionnalité et examinons quelques-uns de ces scénarios.

Activer le débogage asynchrone dans Chrome

Testez cette nouvelle fonctionnalité en l'activant dans Chrome. Accédez au panneau Sources des outils pour les développeurs Chrome Canary.

À côté du panneau Call Stack (Pile d'appel) sur la droite, une nouvelle case à cocher pour "Async" apparaît. Cochez ou décochez la case pour activer ou désactiver le débogage asynchrone (même si cette option est activée, vous ne souhaiterez peut-être plus la désactiver).

Activez ou désactivez la fonctionnalité asynchrone.

Enregistrer les événements de minuteur différés et les réponses XHR

Vous avez probablement déjà vu ce qui suit dans Gmail:

Nouvelle tentative d'envoi d'un e-mail dans Gmail.

En cas de problème lors de l'envoi de la requête (le serveur rencontre des problèmes ou des problèmes de connectivité réseau côté client), Gmail tente automatiquement de renvoyer le message après un court délai d'expiration.

Pour voir comment les piles d'appels asynchrones peuvent nous aider à analyser les événements de minuteur retardés et les réponses XHR, j'ai recréé ce flux avec un exemple Gmail fictif. Le code JavaScript complet est disponible via le lien ci-dessus, mais le flux est le suivant:

Organigramme d'un exemple de simulation Gmail.
Dans le schéma ci-dessus, les méthodes indiquées en bleu sont des points forts pour que cette nouvelle fonctionnalité des outils de développement soit la plus avantageuse, car elles fonctionnent de manière asynchrone.

En consultant uniquement le panneau "Call Stack" (Pile d'appel) des versions précédentes des outils de développement, un point d'arrêt dans postOnFail() vous donnerait peu d'informations sur l'origine de l'appel de postOnFail(). Observez la différence lorsque vous activez les piles asynchrones:

Avant
Point d'arrêt défini dans l'exemple Gmail fictif sans piles d'appels asynchrones
Panneau "Pile d'appel" sans activer l'asynchrone.

Vous pouvez voir ici que postOnFail() a été lancé à partir d'un rappel AJAX, mais qu'aucune autre information n'est fournie.

Après
Point d'arrêt défini dans l'exemple Gmail fictif avec des piles d'appels asynchrones
Panneau "Pile d'appel" avec l'option "asynchrone" activée.

Vous pouvez voir ici que la requête XHR a été lancée à partir de submitHandler(). Nice!

Lorsque les piles d'appels asynchrones sont activées, vous pouvez afficher l'ensemble de la pile d'appel pour voir facilement si la requête a été lancée à partir de submitHandler() (qui se produit après un clic sur le bouton d'envoi) ou de retrySubmit() (qui se produit après un retard de setTimeout()):

submitHandler()
Point d'arrêt défini dans l'exemple Gmail fictif avec des piles d'appels asynchrones
retrySubmit()
Autre point d'arrêt défini dans l'exemple Gmail fictif avec des piles d'appels asynchrones

Observer les expressions de manière asynchrone

Lorsque vous parcourez la pile d'appels complète, vos expressions surveillées sont également mises à jour pour refléter l'état dans lequel elles se trouvaient à ce moment-là.

Exemple d'utilisation d'expressions de contrôle avec des piles d'appels aysnc

Évaluer le code à partir d'anciens niveaux d'accès

En plus de simplement surveiller les expressions, vous pouvez interagir avec le code des champs d'application précédents directement dans le panneau JavaScript des outils de développement.

Imaginez que vous êtes le Dr qui et que vous avez besoin d'un peu d'aide pour comparer l'horloge d'avant votre arrivée au Tardis à aujourd'hui. La console DevTools vous permet d'évaluer, de stocker et d'effectuer facilement des calculs sur des valeurs à partir de différents points d'exécution.

Exemple d'utilisation de la console JavaScript avec des piles d'appels aysnc
Utilisez la console JavaScript avec des piles d'appels asynchrones pour déboguer votre code. Pour accéder à la démonstration ci-dessus, cliquez ici.

En restant dans les outils de développement pour manipuler vos expressions, vous gagnerez du temps, car vous n'aurez pas à revenir au code source, à apporter des modifications et à actualiser le navigateur.

Des résolutions de promesses enchaînées Unravel

Si vous pensiez qu'il était difficile de décomposer l'ancien flux Gmail sans la fonctionnalité de pile d'appel asynchrone activée, imaginez à quel point cela serait plus difficile avec des flux asynchrones plus complexes tels que les promesses enchaînées ? Reprenons le dernier exemple du tutoriel de Jake Archibald sur les promesses JavaScript.

Voici une petite animation permettant de parcourir les piles d'appels dans l'exemple async-best-example.html de Jake.

Avant
Exemple de point d'arrêt défini dans des promesses sans piles d'appels asynchrones
Panneau "Pile d'appel" sans activer l'asynchrone.

Notez que le panneau "Call Stack" (Pile d'appel) manque d'informations lorsque vous essayez de déboguer les promesses.

Après
Exemple de point d'arrêt défini dans des promesses avec des piles d'appels asynchrones
Panneau "Pile d'appel" avec l'option "asynchrone" activée.

Ouah ! De telles promesses. Beaucoup de rappels.

Obtenez des informations sur vos animations Web

Examinons plus en détail les archives HTML5Rocks. Vous souvenez-vous de la série Leaner, Meaner, Faster Animations with requestAnimationFrame de Paul Lewis ?

Ouvrez la démonstration requestAnimationFrame et ajoutez un point d'arrêt au début de la méthode update() (autour de la ligne 874) de post.html. Avec les piles d'appels asynchrones, nous obtenons beaucoup plus d'informations sur requestAnimationFrame, y compris la possibilité de revenir au rappel de l'événement de défilement.

Avant
Point d'arrêt défini dans l'exemple requestAnimationFrame sans piles d'appels asynchrones.
Panneau "Pile d'appel" sans activer l'asynchrone.
Après
Exemple de point d'arrêt défini dans l'exemple requestAnimationFrame avec des piles d'appels asynchrones
Et avec l'asynchrone activée.

Retrouver les mises à jour DOM lors de l'utilisation de MutationObserver

MutationObserver nous permet d'observer les modifications dans le DOM. Dans cet exemple simple, lorsque vous cliquez sur le bouton, un nouveau nœud DOM est ajouté à <div class="rows"></div>.

Ajoutez un point d'arrêt dans nodeAdded() (ligne 31) dans demo.html. Lorsque les piles d'appels asynchrones sont activées, vous pouvez maintenant renvoyer la pile d'appel via addNode() jusqu'à l'événement de clic initial.

Avant
Point d&#39;arrêt défini dans l&#39;exemple de mutationObserver sans piles d&#39;appels asynchrones.
Panneau "Pile d'appel" sans activer l'asynchrone.
Après
Exemple de point d&#39;arrêt défini dans l&#39;exemple de mutationObserver avec des piles d&#39;appels asynchrones.
Et avec l'asynchrone activée.

Conseils pour déboguer JavaScript dans les piles d'appels asynchrones

Nommer vos fonctions

Si vous avez tendance à attribuer tous vos rappels en tant que fonctions anonymes, vous pouvez leur attribuer un nom pour faciliter l'affichage de la pile d'appels.

Prenons l'exemple d'une fonction anonyme comme celle-ci:

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

Donnez-lui un nom tel que windowLoaded():

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

Lorsque l'événement de chargement se déclenche, il apparaît dans la trace de la pile des outils de développement avec son nom de fonction au lieu de l'énigmatique (fonction anonyme). Il est ainsi beaucoup plus facile de voir d'un coup d'œil ce qui se passe dans votre trace de la pile.

Avant
Une fonction anonyme.
Après
Une fonction nommée

En savoir plus

Pour récapituler, voici tous les rappels asynchrones dans lesquels les outils de développement afficheront la pile d'appel complète:

  • Minuteurs : revenez à l'endroit où setTimeout() ou setInterval() a été initialisé.
  • XHR : revenez à l'endroit où xhr.send() a été appelé.
  • Images d'animation : revenez à l'endroit où requestAnimationFrame a été appelé.
  • Promesses : revenez là où une promesse a été résolue.
  • Object.observe : revenez à l'endroit où le rappel de l'observateur était initialement lié.
  • MutationObservers : revenez à l'endroit où l'événement d'observateur de mutation a été déclenché.
  • window.postMessage(): suivez les appels de messagerie au sein du processus.
  • DataTransferItem.getAsString()
  • API FileSystem
  • IndexedDB
  • WebSQL
  • Événements DOM éligibles via addEventListener() : revenez à l'endroit où l'événement a été déclenché. Pour des raisons de performances, tous les événements DOM ne sont pas éligibles à la fonctionnalité des piles d'appels asynchrones. Voici quelques exemples d'événements actuellement disponibles: "scroll", "hashchange" et "selectionchange".
  • Événements multimédias via addEventListener() : revenez à l'endroit où l'événement a été déclenché. Les événements multimédias disponibles incluent les événements audio et vidéo (par exemple, "play", "pause", "ratechange"), les événements WebRTC MediaStreamTrackList (par exemple, "addtrack", "removetrack") et les événements MediaSource (par exemple, "sourceopen").

La possibilité de voir la trace de la pile complète de vos rappels JavaScript devrait vous faciliter la tâche. Cette fonctionnalité des outils de développement est particulièrement utile lorsque plusieurs événements asynchrones se produisent les uns par rapport aux autres ou si une exception non détectée est générée depuis un rappel asynchrone.

Essayez-le dans Chrome. Si vous avez des commentaires sur cette nouvelle fonctionnalité, envoyez-nous un message sur l'outil de suivi des bugs des outils pour les développeurs Chrome ou dans le groupe des outils pour les développeurs Chrome.