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.
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.)
Capturer les événements de minuteur différés et les réponses XHR
Vous avez probablement déjà vu ce message dans Gmail:
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:
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:
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()
):
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à.
É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.
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.
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.
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.
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.
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()
ousetInterval()
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.