Fehlerbehebung bei asynchronem JavaScript mit Chrome-Entwicklertools

Pearl Chen

Einführung

Eine leistungsstarke Funktion, die JavaScript einzigartig macht, ist die Möglichkeit, asynchron über Callback-Funktionen zu arbeiten. Durch das Zuweisen von asynchronen Callbacks können Sie ereignisgesteuerten Code schreiben. Das Aufspüren von Fehlern ist jedoch sehr mühsam, da der JavaScript-Code nicht linear ausgeführt wird.

Glücklicherweise können Sie sich jetzt in den Chrome-Entwicklertools den vollständigen Aufrufstapel von asynchronen JavaScript-Callbacks ansehen.

Kurzer Überblick über asynchrone Aufrufstacks
Kurzer Überblick über asynchrone Aufrufstacks. (Wir gehen gleich noch genauer auf den Ablauf dieser Demo ein.)

Sobald Sie die Funktion „Async Call Stack“ in den DevTools aktiviert haben, können Sie den Status Ihrer Webanwendung zu verschiedenen Zeitpunkten untersuchen. Vollständigen Stack-Trace für einige Ereignis-Listener, setInterval, setTimeout, XMLHttpRequest, Versprechen, requestAnimationFrame, MutationObservers und mehr aufrufen

Beim Durchlaufen des Stack-Traces können Sie auch den Wert einer beliebigen Variablen an diesem bestimmten Punkt der Laufzeitausführung analysieren. Es ist wie eine Zeitmaschine für Ihre Zifferblätter.

Aktivieren wir diese Funktion und sehen uns einige dieser Szenarien an.

Async-Fehlerbehebung in Chrome aktivieren

Sie können diese neue Funktion in Chrome aktivieren und ausprobieren. Rufen Sie in den Chrome Canary-Entwicklertools den Bereich Quellen auf.

Rechts neben dem Bereich Call Stack (Anrufstapel) befindet sich ein neues Kästchen für „Async“ (Asynchron). Aktivieren oder deaktivieren Sie das Kästchen, um das asynchrone Debuggen zu aktivieren oder zu deaktivieren. Wenn Sie es einmal aktiviert haben, sollten Sie es jedoch nicht mehr deaktivieren.

Aktivieren oder deaktivieren Sie die asynchrone Funktion.

Verzögerte Timer-Ereignisse und XHR-Antworten erfassen

Das haben Sie in Gmail wahrscheinlich schon einmal gesehen:

Gmail versucht noch einmal, eine E-Mail zu senden.

Wenn beim Senden der Anfrage ein Problem auftritt (entweder auf dem Server oder auf der Clientseite aufgrund von Netzwerkverbindungsproblemen), versucht Gmail nach einer kurzen Zeitüberschreitung automatisch, die Nachricht noch einmal zu senden.

Um zu sehen, wie asynchrone Aufrufstapel uns bei der Analyse verzögerter Timerereignisse und XHR-Antworten helfen können, habe ich diesen Ablauf mit einem Mock-Gmail-Beispiel nachgestellt. Den vollständigen JavaScript-Code findest du über den Link oben. Der Ablauf ist jedoch so:

Flussdiagramm für ein Beispiel für eine gefälschte Gmail-Anfrage.
Im Diagramm oben sind die blau hervorgehobenen Methoden die besten Stellen, an denen diese neue DevTool-Funktion am nützlichsten ist, da sie asynchron arbeiten.

In früheren Versionen von DevTools gab ein Haltepunkt in postOnFail() nur wenig Aufschluss darüber, woher postOnFail() aufgerufen wurde. Sehen Sie sich aber den Unterschied an, wenn Sie asynchrone Stacks aktivieren:

Vor
In diesem Beispiel für eine simulierte Gmail-Anwendung wurde ein Haltepunkt ohne asynchrone Aufrufstapel gesetzt.
Der Bereich „Call Stack“ (Aufrufstapel) ohne aktivierte Async-Funktion.

Hier sehen Sie, dass postOnFail() über einen AJAX-Callback initiiert wurde, aber keine weiteren Informationen.

Nach
In diesem Beispiel für eine simulierte Gmail-Anwendung mit asynchronen Aufrufstapeln ist ein Haltepunkt gesetzt.
Der Bereich „Call Stack“ mit aktivierter Async-Funktion.

Hier sehen Sie, dass die XHR-Anfrage von submitHandler() aus gestartet wurde. Sehr gut!

Wenn asynchrone Aufrufstacks aktiviert sind, können Sie den gesamten Aufrufstack aufrufen, um leicht zu erkennen, ob die Anfrage von submitHandler() (nach dem Klicken auf die Schaltfläche „Senden“) oder von retrySubmit() (nach einer Verzögerung von setTimeout()) gestartet wurde:

submitHandler()
In einem simulierten Gmail-Beispiel mit asynchronen Aufrufstapeln gesetzter Haltepunkt
retrySubmit()
Ein weiterer Breakpoint im simulierten Gmail-Beispiel mit asynchronen Aufrufstapeln

Watch-Ausdrücke asynchron auswerten

Wenn Sie den gesamten Aufrufstapel durchgehen, werden auch Ihre beobachteten Ausdrücke aktualisiert, um den Status zu diesem Zeitpunkt widerzuspiegeln.

Beispiel für die Verwendung von Watch-Ausdrücken mit asynchronen Aufrufstapeln

Code aus früheren Bereichen auswerten

Sie können Ausdrücke nicht nur beobachten, sondern auch direkt in der JavaScript-Konsole der Entwicklertools mit Ihrem Code aus vorherigen Bereichen interagieren.

Stellen Sie sich vor, Sie sind Dr. Who und benötigen ein wenig Hilfe beim Vergleichen der Uhr von vor dem Einsteigen in die Tardis mit der Uhrzeit „jetzt“. In der DevTools-Konsole können Sie Werte aus verschiedenen Ausführungspunkten ganz einfach auswerten, speichern und berechnen.

Beispiel für die Verwendung der JavaScript-Konsole mit asynchronen Aufrufstapeln
Verwenden Sie die JavaScript-Konsole in Verbindung mit asynchronen Aufrufstapeln, um Ihren Code zu debuggen. Die Demo oben finden Sie hier.

Wenn Sie Ihre Ausdrücke in den Entwicklertools bearbeiten, sparen Sie Zeit, da Sie nicht zum Quellcode zurückkehren, Änderungen vornehmen und den Browser aktualisieren müssen.

Verkettete Promise-Auflösungen entwirren

Wenn Sie dachten, dass der vorherige Mock-Gmail-Vorgang ohne aktivierte Funktion für den asynchronen Aufrufstapel schwer zu entwirren war, können Sie sich vorstellen, wie viel schwieriger es bei komplexeren asynchronen Abläufen wie verketteten Versprechen wäre? Sehen wir uns noch einmal das letzte Beispiel aus Jake Archibalds Anleitung zu JavaScript-Versprechen an.

Hier ist eine kleine Animation, die die Aufrufsstacks in Jakes Beispiel async-best-example.html durchläuft.

Vor
Im Beispiel für Versprechen gesetzter Haltepunkt ohne Stapel von asynchronen Aufrufen
Der Bereich „Call Stack“ ohne aktivierte Async-Funktion.

Beachten Sie, dass das Bereich „Aufrufstapel“ beim Debuggen von Versprechen nur wenige Informationen enthält.

Nach
Im Beispiel für Versprechen mit asynchronen Aufrufstapeln gesetzter Haltepunkt
Der Bereich „Call Stack“ mit aktivierter Async-Funktion.

Wow! Solche Versprechen. Viele Rückrufe.

Statistiken zu Webanimationen abrufen

Sehen wir uns die HTML5Rocks-Archive genauer an. Erinnern Sie sich an Paul Lewis' Leaner, Meaner, Faster Animations with requestAnimationFrame?

Öffnen Sie die Demo für requestAnimationFrame und fügen Sie am Anfang der Methode update() (etwa in Zeile 874) von post.html einen Haltepunkt hinzu. Mit asynchronen Aufrufstacks erhalten wir viel mehr Informationen zu requestAnimationFrame, einschließlich der Möglichkeit, bis zum Callback des einleitenden Scrollereignisses zurückzuverfolgen.

Vor
Im Beispiel für requestAnimationFrame eingefügter Haltepunkt ohne asynchrone Aufrufstacks
Der Bereich „Call Stack“ ohne aktivierte Async-Funktion.
Nach
Im Beispiel für requestAnimationFrame festgelegter Haltepunkt mit asynchronen Aufrufstacks
und mit aktivierter Async-Funktion.

DOM-Änderungen bei Verwendung von MutationObserver ermitteln

MutationObserver ermöglichen es uns, Änderungen im DOM zu beobachten. In diesem einfachen Beispiel wird durch Klicken auf die Schaltfläche <div class="rows"></div> ein neuer DOM-Knoten angehängt.

Fügen Sie in demo.html in nodeAdded() (Zeile 31) einen Haltepunkt hinzu. Wenn asynchrone Aufrufstacks aktiviert sind, können Sie den Aufrufstapel jetzt über addNode() bis zum ursprünglichen Klickereignis zurückverfolgen.

Vor
Im Beispiel für mutationObserver wurde ein Haltepunkt ohne Stapel von asynchronen Aufrufen gesetzt.
Der Bereich „Call Stack“ ohne aktivierte Async-Funktion.
Nach
Haltepunkt im Beispiel für mutationObserver mit asynchronen Aufrufstapeln
und mit aktivierter Async-Funktion.

Tipps zur Fehlerbehebung bei JavaScript in asynchronen Aufrufstapeln

Funktionen benennen

Wenn Sie alle Callbacks als anonyme Funktionen zuweisen, sollten Sie ihnen einen Namen geben, damit der Aufrufstack leichter zu lesen ist.

Hier ein Beispiel für eine anonyme Funktion:

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

Geben Sie ihm einen Namen wie windowLoaded():

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

Wenn das Ereignis „load“ ausgelöst wird, wird es im DevTools-Stack-Trace mit dem Funktionsnamen anstelle des kryptischen „(anonyme Funktion)“ angezeigt. So lässt sich auf einen Blick leichter erkennen, was im Stack-Trace passiert.

Vorher
Eine anonyme Funktion.
Nachher
Eine benannte Funktion

Weitere Informationen

Hier noch einmal alle asynchronen Rückrufe, bei denen in DevTools der vollständige Aufrufstapel angezeigt wird:

  • Timer: Gehen Sie zurück zu dem Punkt, an dem setTimeout() oder setInterval() initialisiert wurde.
  • XHRs: Gehen Sie zurück zu dem Punkt, an dem xhr.send() aufgerufen wurde.
  • Animationsframes: Gehen Sie zurück zu der Stelle, an der requestAnimationFrame aufgerufen wurde.
  • Versprechen: Gehen Sie zurück zu der Stelle, an der ein Versprechen aufgelöst wurde.
  • Object.observe: Gehen Sie zurück zu der Stelle, an der der Beobachter-Callback ursprünglich gebunden wurde.
  • MutationObservers: Gehen Sie zurück zu dem Punkt, an dem das Mutation Observer-Ereignis ausgelöst wurde.
  • window.postMessage(): Durchlaufen von Aufrufen von intraprozeduralen Messaging-Funktionen.
  • DataTransferItem.getAsString()
  • FileSystem API
  • IndexedDB
  • WebSQL
  • Zulässige DOM-Ereignisse über addEventListener(): Gehen Sie zurück zum Ort, an dem das Ereignis ausgelöst wurde. Aus Leistungsgründen sind nicht alle DOM-Ereignisse für die Funktion „Asynchroner Aufrufstapel“ geeignet. Beispiele für derzeit verfügbare Ereignisse sind „scroll“, „hashchange“ und „selectionchange“.
  • Multimedia-Ereignisse über addEventListener(): Gehen Sie zurück zum Ursprung des Ereignisses. Zu den verfügbaren Multimedia-Ereignissen gehören Audio- und Videoereignisse (z.B. „play“, „pause“, „ratechange“), WebRTC MediaStreamTrackList-Ereignisse (z.B. „addtrack“, „removetrack“) und MediaSource-Ereignisse (z.B. „sourceopen“).

Wenn Sie den vollständigen Stack-Trace Ihrer JavaScript-Callbacks sehen können, sollten Sie Ihre Haare behalten. Diese Funktion in den DevTools ist besonders hilfreich, wenn mehrere asynchrone Ereignisse in Bezug zueinander auftreten oder wenn eine nicht aufgefangene Ausnahme innerhalb eines asynchronen Rückrufs ausgelöst wird.

Probieren Sie es in Chrome aus. Wenn Sie Feedback zu dieser neuen Funktion haben, können Sie uns dies im Fehler-Tracker der Chrome-Entwicklertools oder in der Chrome-Entwicklertools-Gruppe mitteilen.