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

Introduction

JavaScript se distingue par une fonctionnalité puissante : 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 les événements, mais elle rend également la recherche de bugs fastidieuse, car le code JavaScript n'est pas exécuté 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 allons bientôt expliquer le déroulement de cette démonstration.)

Une fois que vous avez activé la fonctionnalité de pile d'appels asynchrones dans DevTools, vous pouvez examiner l'état de votre application Web à différents moments. Parcourez la trace de pile complète pour certains écouteurs d'événements, setInterval, setTimeout, XMLHttpRequest, promesses, requestAnimationFrame, MutationObservers et plus encore.

Lorsque vous parcourez la trace de la pile, vous pouvez également analyser la valeur de n'importe quelle variable à ce point particulier de l'exécution. C'est comme une machine à remonter le temps pour vos expressions de cadran.

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

Activer le débogage asynchrone dans Chrome

Essayez 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'appels) sur le côté droit, une nouvelle case à cocher est disponible pour "Async" (Async). Cochez ou décochez la case pour activer ou désactiver le débogage asynchrone. (Une fois activé, vous ne voudrez peut-être plus jamais le désactiver.)

Activez ou désactivez la fonctionnalité asynchrone.

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

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

Gmail tente à nouveau d'envoyer un e-mail.

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

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

Organigramme de l'exemple fictif de Gmail.
Dans le diagramme ci-dessus, les méthodes en bleu sont les endroits où cette nouvelle fonctionnalité DevTool est la plus bénéfique, car elles fonctionnent de manière asynchrone.

En examinant uniquement le panneau "Call Stack" (Pile d'appels) des versions précédentes de DevTools, un point d'arrêt dans postOnFail() ne vous fournissait que peu d'informations sur l'origine de l'appel de postOnFail(). Mais regardez la différence lorsque vous activez les piles asynchrones:

Avant
Point d'arrêt défini dans l'exemple de simulation de Gmail sans piles d'appels asynchrones.
Panneau "Call Stack" (Pile d'appels) sans l'option asynchrone activée.

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

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

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

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

submitHandler()
Point d'arrêt défini dans un 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

Expressions de contrôle 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 surveillance avec des piles d'appels asynchrones

Évaluer le code des portées précédentes

En plus de simplement surveiller les expressions, vous pouvez interagir avec votre code à partir d'étendues précédentes directement dans le panneau de la console JavaScript de DevTools.

Imaginez que vous êtes le Docteur Who et que vous avez besoin d'un peu d'aide pour comparer l'horloge d'avant que vous ne montiez dans le Tardis à "maintenant". Dans la console DevTools, vous pouvez facilement évaluer, stocker et effectuer des calculs sur les valeurs de différents points d'exécution.

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

En restant dans DevTools pour manipuler vos expressions, vous économisez du temps, car vous n'avez pas besoin de revenir à votre code source, de le modifier et d'actualiser le navigateur.

Démêler les résolutions de promesses en chaîne

Si vous pensiez que le flux Gmail fictif précédent était difficile à démêler sans la fonctionnalité de pile d'appels asynchrones activée, imaginez combien il serait plus difficile avec des flux asynchrones plus complexes, comme les promesses en chaîne. Revoyons l'exemple final du tutoriel de Jake Archibald sur les promesses JavaScript.

Voici une petite animation de parcours des 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 "Call Stack" (Pile d'appels) sans l'option asynchrone activée.

Notez que le panneau "Call Stack" (Pile d'appels) ne contient que peu d'informations lorsque vous essayez de déboguer des promesses.

Après
Point d'arrêt défini dans l'exemple de promesses avec des piles d'appels asynchrones.
Panneau "Call Stack" (Pile des appels) avec l'option asynchrone activée.

Impressionnant ! Quelles promesses ! Trop de rappels.

Obtenir des insights sur vos animations Web

Allons plus loin dans les archives HTML5Rocks. Vous souvenez-vous de l'article de Paul Lewis sur les animations plus efficaces, plus rapides et plus percutantes avec requestAnimationFrame ?

Ouvrez la démo requestAnimationFrame et ajoutez un point d'arrêt au début de la méthode update() (vers 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 remonter jusqu'au rappel de l'événement de défilement initial.

Avant
Point d'arrêt défini dans l'exemple requestAnimationFrame sans piles d'appels asynchrones.
Panneau "Call Stack" (Pile d'appels) sans l'option asynchrone activée.
Après
Point d'arrêt défini dans l'exemple requestAnimationFrame avec des piles d'appels asynchrones
Et avec l'option asynchrone activée.

Détecter les mises à jour du DOM lorsque vous utilisez MutationObserver

MutationObserver nous permet d'observer les modifications apportées au 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 désormais remonter la pile d'appels via addNode() jusqu'à l'événement de clic initial.

Avant
Point d&#39;arrêt défini dans l&#39;exemple mutationObserver sans piles d&#39;appels asynchrones.
Panneau "Call Stack" (Pile d'appels) sans l'option asynchrone activée.
Après
Point d&#39;arrêt défini dans l&#39;exemple mutationObserver avec des piles d&#39;appels asynchrones.
Et avec l'option 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:

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

Attribuez-lui un nom, par exemple windowLoaded():

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

Lorsque l'événement de chargement se déclenche, il s'affiche dans la trace de la pile DevTools avec son nom de fonction au lieu de l'élément cryptique (fonction anonyme). Cela permet de voir plus facilement en 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 DevTools affiche la pile d'appels complète:

  • Minuteurs : revenez à l'endroit où setTimeout() ou setInterval() a été initialisé.
  • XHR : revenez à l'emplacement où xhr.send() a été appelé.
  • Cadres d'animation : revenez à l'endroit où requestAnimationFrame a été appelé.
  • Promesses : revenez à l'endroit où une promesse a été résolue.
  • Object.observe : remontez jusqu'à l'endroit où le rappel de l'observateur était initialement lié.
  • MutationObservers : remontez jusqu'à l'emplacement où l'événement de l'observateur de mutation a été déclenché.
  • window.postMessage(): parcourez les appels de messagerie intra-processus.
  • DataTransferItem.getAsString()
  • API FileSystem
  • IndexedDB
  • WebSQL
  • Événements DOM éligibles via addEventListener() : remontez jusqu'à l'emplacement où l'événement a été déclenché. Pour des raisons de performances, tous les événements DOM ne sont pas éligibles à la fonctionnalité de piles d'appels asynchrones. Exemples d'événements actuellement disponibles: "scroll", "hashchange" et "selectionchange".
  • Événements multimédias via addEventListener() : revenez à l'emplacement 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 éviter de perdre vos cheveux. Cette fonctionnalité de DevTools 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 à partir d'un rappel asynchrone.

Essayez-la dans Chrome. Si vous avez des commentaires sur cette nouvelle fonctionnalité, n'hésitez pas à nous en faire part dans l'outil de suivi des bugs des outils pour les développeurs Chrome ou dans le groupe des outils pour les développeurs Chrome.