Foutopsporing in asynchrone JavaScript met Chrome DevTools

Invoering

Een krachtige functie die JavaScript uniek maakt, is de mogelijkheid om asynchroon te werken via callback-functies. Door asynchrone callbacks toe te wijzen, kun je gebeurtenisgestuurde code schrijven, maar het opsporen van bugs wordt ook een huiveringwekkende ervaring, omdat JavaScript niet op een lineaire manier wordt uitgevoerd.

Gelukkig kun je nu in Chrome DevTools de volledige call-stack van asynchrone JavaScript-callbacks bekijken!

Een snel teaseroverzicht van asynchrone call-stacks.
Een snel teaseroverzicht van asynchrone call-stacks. (We zullen de stroom van deze demo binnenkort afbreken.)

Zodra u de asynchrone call-stack-functie in DevTools inschakelt, kunt u op verschillende tijdstippen inzoomen op de status van uw web-app. Voer de volledige stacktrace uit voor sommige gebeurtenislisteners, setInterval , setTimeout , XMLHttpRequest , beloftes, requestAnimationFrame , MutationObservers en meer.

Terwijl u de stacktrace doorloopt, kunt u ook de waarde van elke variabele op dat specifieke punt van runtime-uitvoering analyseren. Het is als een tijdmachine voor je horloge-uitdrukkingen!

Laten we deze functie inschakelen en een paar van deze scenario's bekijken.

Schakel asynchrone foutopsporing in Chrome in

Probeer deze nieuwe functie uit door deze in Chrome in te schakelen. Ga naar het Bronnenpaneel van Chrome Canary DevTools.

Naast het Call Stack- paneel aan de rechterkant is er een nieuw selectievakje voor "Async". Schakel het selectievakje in om asynchrone foutopsporing in of uit te schakelen. (Hoewel als het eenmaal is ingeschakeld, je het misschien nooit meer wilt uitschakelen.)

Schakel de asynchrone functie in of uit.

Leg vertraagde timergebeurtenissen en XHR-reacties vast

Je hebt dit waarschijnlijk al eerder gezien in Gmail:

Gmail probeert opnieuw een e-mail te verzenden.

Als er een probleem is met het verzenden van het verzoek (de server heeft problemen of er zijn problemen met de netwerkverbinding aan de clientzijde), zal Gmail na een korte time-out automatisch proberen het bericht opnieuw te verzenden.

Om te zien hoe asynchrone oproepstapels ons kunnen helpen bij het analyseren van vertraagde timergebeurtenissen en XHR-reacties, heb ik die stroom opnieuw gemaakt met een nep-Gmail-voorbeeld . De volledige JavaScript-code is te vinden in de bovenstaande link, maar de stroom is als volgt:

Stroomdiagram van een nep-Gmail-voorbeeld.
In het bovenstaande diagram zijn de blauw gemarkeerde methoden de belangrijkste plekken voor deze nieuwe DevTool-functie om het meest nuttig te zijn, aangezien deze methoden asynchroon werken.

Door alleen naar het Call Stack-paneel in eerdere versies van DevTools te kijken, zou een breekpunt binnen postOnFail() u weinig informatie geven over waar postOnFail() vandaan werd aangeroepen. Maar kijk eens naar het verschil bij het inschakelen van asynchrone stapels:

Voor
Breekpunt ingesteld in nep-Gmail-voorbeeld zonder asynchrone aanroepstapels.
Het Call Stack-paneel zonder async ingeschakeld.

Hier kun je zien dat postOnFail() is geïnitieerd vanuit een AJAX-callback, maar geen verdere informatie.

Na
Breekpunt ingesteld in een nep-Gmail-voorbeeld met asynchrone oproepstapels.
Het Call Stack-paneel met async ingeschakeld.

Hier kunt u zien dat de XHR is geïnitieerd vanuit submitHandler() . Leuk!

Als asynchrone call-stacks zijn ingeschakeld, kunt u de gehele call-stack bekijken om eenvoudig te zien of het verzoek is geïnitieerd vanuit submitHandler() (wat gebeurt na het klikken op de verzendknop) of vanuit retrySubmit() (wat gebeurt na een setTimeout() -vertraging) :

SubmitHandler()
Breekpunt ingesteld in een nep-Gmail-voorbeeld met asynchrone oproepstapels
opnieuw proberenVerzenden()
Een ander breekpunt dat is ingesteld in een nep-Gmail-voorbeeld met asynchrone oproepstapels

Bekijk expressies asynchroon

Wanneer u de volledige call-stack doorloopt, worden uw bekeken uitdrukkingen ook bijgewerkt om de staat weer te geven waarin deze zich op dat moment bevonden!

Een voorbeeld van het gebruik van watch-expressies met aysnc-oproepstapels

Evalueer code uit eerdere bereiken

Naast het eenvoudig bekijken van expressies, kunt u direct in het DevTools JavaScript-consolepaneel communiceren met uw code uit eerdere scopes.

Stel je voor dat je Dr. Who bent en dat je wat hulp nodig hebt bij het vergelijken van de klok van voordat je in de Tardis stapte met "nu". Vanuit de DevTools-console kunt u eenvoudig waarden van verschillende uitvoeringspunten evalueren, opslaan en berekeningen uitvoeren.

Een voorbeeld van het gebruik van de JavaScript-console met aysnc-aanroepstacks.
Gebruik de JavaScript-console in combinatie met asynchrone aanroepstacks om fouten in uw code op te sporen. Bovenstaande demo vindt u hier .

Als u binnen DevTools blijft om uw expressies te manipuleren, bespaart u tijd omdat u niet hoeft terug te schakelen naar uw broncode, wijzigingen moet aanbrengen en de browser moet vernieuwen.

Ontrafel geketende belofte-resoluties

Als je dacht dat de vorige nep-Gmail-stroom moeilijk te ontrafelen was zonder dat de asynchrone call-stack-functie was ingeschakeld, kun je je dan voorstellen hoeveel moeilijker het zou zijn met complexere asynchrone stromen zoals aan elkaar gekoppelde beloften? Laten we het laatste voorbeeld van Jake Archibald's tutorial over JavaScript Promises nog eens bekijken.

Hier is een kleine animatie van het doorlopen van de call-stacks in Jake's async-best-example.html voorbeeld.

Voor
Breekpunt ingesteld in het beloftenvoorbeeld zonder asynchrone call-stacks
Het Call Stack-paneel zonder async ingeschakeld.

Merk op dat het Call Stack-paneel vrij weinig informatie bevat bij het debuggen van beloften.

Na
Breekpunt ingesteld in het beloftenvoorbeeld met asynchrone call-stacks.
Het Call Stack-paneel met async ingeschakeld.

Wauw! Zulke beloftes. Veel terugbelacties.

Krijg inzicht in uw webanimaties

Laten we dieper ingaan op de HTML5Rocks-archieven. Herinner je de slankere, gemenere, snellere animaties van Paul Lewis met requestAnimationFrame ?

Open de demo requestAnimationFrame en voeg een breekpunt toe aan het begin van de update() -methode (rond regel 874) van post.html. Met asynchrone call-stacks krijgen we veel meer inzicht in requestAnimationFrame , inclusief de mogelijkheid om helemaal terug te lopen naar de initiërende scroll-gebeurtenis-callback.

Voor
Breekpunt ingesteld in requestAnimationFrame-voorbeeld zonder asynchrone aanroepstapels.
Het Call Stack-paneel zonder async ingeschakeld.
Na
Breekpunt ingesteld in requestAnimationFrame-voorbeeld met asynchrone aanroepstapels
En met async ingeschakeld.

Spoor DOM-updates op wanneer u MutationObserver gebruikt

MutationObserver kunnen we veranderingen in de DOM waarnemen. In dit eenvoudige voorbeeld wordt, wanneer u op de knop klikt, een nieuw DOM-knooppunt toegevoegd aan <div class="rows"></div> .

Voeg een breekpunt toe binnen nodeAdded() (regel 31) in demo.html. Als asynchrone call-stacks zijn ingeschakeld, kunt u de call-stack nu terugsturen via addNode() naar de initiële klikgebeurtenis.

Voor
Breekpunt ingesteld in het MutationObserver-voorbeeld zonder asynchrone aanroepstapels.
Het Call Stack-paneel zonder async ingeschakeld.
Na
Breekpunt ingesteld in het MutationObserver-voorbeeld met asynchrone aanroepstapels.
En met async ingeschakeld.

Tips voor het debuggen van JavaScript in asynchrone call-stacks

Geef uw functies een naam

Als u de neiging heeft om al uw callbacks als anonieme functies toe te wijzen, kunt u ze in plaats daarvan een naam geven om het bekijken van de call-stack eenvoudiger te maken.

Neem bijvoorbeeld een anonieme functie als deze:

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

En geef het een naam zoals windowLoaded() :

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

Wanneer de load-gebeurtenis wordt geactiveerd, wordt deze weergegeven in de DevTools-stacktrace met de functienaam in plaats van de cryptische " (anonieme functie) ". Dit maakt het veel gemakkelijker om in één oogopslag te zien wat er in uw stacktrace gebeurt.

Voor
Een anonieme functie.
Na
Een benoemde functie

Ontdek verder

Om samen te vatten: dit zijn alle asynchrone callbacks waarin DevTools de volledige call-stack zal weergeven:

  • Timers : Loop terug naar waar setTimeout() of setInterval() is geïnitialiseerd.
  • XHRs : Loop terug naar waar xhr.send() werd aangeroepen.
  • Animatieframes : loop terug naar waar requestAnimationFrame is aangeroepen.
  • Beloften : Loop terug naar waar een belofte is opgelost.
  • Object.observe : Loop terug naar waar de callback van de waarnemer oorspronkelijk was gebonden.
  • MutationObservers : Loop terug naar de plaats waar de Mutation Observer-gebeurtenis werd geactiveerd.
  • window.postMessage() : Loop over berichtenoproepen binnen het proces.
  • DataTransferItem.getAsString()
  • Bestandssysteem-API
  • GeïndexeerdeDB
  • WebSQL
  • Geschikte DOM-gebeurtenissen via addEventListener() : Loop terug naar waar de gebeurtenis is geactiveerd. Vanwege prestatieredenen komen niet alle DOM-gebeurtenissen in aanmerking voor de functie voor asynchrone call-stacks. Voorbeelden van momenteel beschikbare gebeurtenissen zijn: 'scroll', 'hashchange' en 'selectionchange'.
  • Multimediagebeurtenissen via addEventListener() : Loop terug naar waar de gebeurtenis werd geactiveerd. Beschikbare multimedia-evenementen zijn onder meer: ​​audio- en video-evenementen (bijv. 'play', 'pause', 'ratechange'), WebRTC MediaStreamTrackList-evenementen (bijv. 'addtrack', 'removetrack') en MediaSource-evenementen (bijv. 'sourceopen').

Het kunnen zien van de volledige stacktrace van uw JavaScript-callbacks zou de haren op uw hoofd moeten houden. Deze functie in DevTools is vooral handig als er meerdere asynchrone gebeurtenissen plaatsvinden in relatie tot elkaar, of als er een niet-afgevangen uitzondering wordt gegenereerd vanuit een asynchrone callback.

Probeer het eens in Chrome. Als u feedback heeft over deze nieuwe functie, kunt u ons een bericht sturen via de Chrome DevTools -bugtracker of in de Chrome DevTools Group .