Como depurar JavaScript assíncrono com o Chrome DevTools

Introdução

Um recurso eficiente que torna o JavaScript único é a capacidade de funcionar de maneira assíncrona, usando funções de callback. A atribuição de callbacks assíncronos permite que você escreva um código orientado por eventos, mas também transforma o rastreamento de bugs em uma experiência difícil, já que o JavaScript não está sendo executado de maneira linear.

Felizmente, agora no Chrome DevTools, você pode conferir a pilha de chamadas completa de callbacks JavaScript assíncronos.

Um teaser rápido das pilhas de chamadas assíncronas.
Um teaser rápido das pilhas de chamadas assíncronas. Detalharemos o fluxo desta demonstração em breve.

Depois de ativar o recurso de pilha de chamadas assíncrona no DevTools, você poderá analisar o estado do seu app da Web em vários momentos. Acompanhe o stack trace completo para alguns listeners de eventos, setInterval, setTimeout, XMLHttpRequest, promessas, requestAnimationFrame, MutationObservers e muito mais.

À medida que avança no stack trace, também é possível analisar o valor de qualquer variável nesse ponto específico de execução do ambiente de execução. É como uma máquina do tempo para suas expressões de relógio.

Vamos ativar esse recurso e analisar alguns cenários.

Ativar a depuração assíncrona no Chrome

Ative o novo recurso no Chrome para testar. Acesse o painel Sources do Chrome Canary DevTools.

Ao lado do painel Call Stack à direita, há uma nova caixa de seleção para "Async". Marque a caixa de seleção para ativar ou desativar a depuração assíncrona. Embora depois de ativada, talvez você não queira desativá-la.

Ative ou desative o recurso assíncrono.

Capturar eventos de timer atrasados e respostas XHR

Provavelmente você já viu isto no Gmail:

O Gmail está tentando enviar um e-mail novamente.

Se houver um problema ao enviar a solicitação, seja no servidor ou na conectividade de rede do lado do cliente, o Gmail tentará automaticamente reenviar a mensagem após um curto tempo limite.

Para acessar como as pilhas de chamadas assíncronas podem nos ajudar a analisar eventos de timer atrasado e respostas XHR, recriei esse fluxo com um exemplo fictício do Gmail. O código JavaScript completo pode ser encontrado no link acima, mas o fluxo é este:

Fluxograma com exemplo de simulação do Gmail.
No diagrama acima, os métodos destacados em azul são os principais pontos para que esse novo recurso da DevTool seja os mais benéficos, já que eles funcionam de forma assíncrona.

Analisando apenas o painel "Call Stack" nas versões anteriores do DevTools, um ponto de interrupção em postOnFail() forneceria pouca informação sobre de onde postOnFail() estava sendo chamado. Mas observe a diferença ao ativar pilhas assíncronas:

Antes
Ponto de interrupção definido em um exemplo de simulação do Gmail sem pilhas de chamadas assíncronas.
O painel "Call Stack" sem a ativação assíncrona.

Aqui é possível ver que postOnFail() foi iniciado por um callback AJAX, mas sem mais informações.

Depois
Ponto de interrupção definido em um exemplo de simulação do Gmail com pilhas de chamadas assíncronas.
O painel "Call Stack" com o recurso assíncrono ativado.

Aqui é possível observar que o XHR foi iniciado em submitHandler(). Legal!

Com as pilhas de chamadas assíncronas ativadas, você pode conferir toda a pilha de chamadas para conferir facilmente se a solicitação foi iniciada por submitHandler() (o que acontece depois de clicar no botão "Enviar") ou retrySubmit() (que acontece após um atraso de setTimeout()):

submitHandler()
Ponto de interrupção definido em exemplo de simulação do Gmail com pilhas de chamadas assíncronas
retrySubmit()
Outro ponto de interrupção definido em um exemplo de simulação do Gmail com pilhas de chamadas assíncronas

Observar expressões de maneira assíncrona

Quando você percorre a pilha de chamadas completa, as expressões monitoradas também são atualizadas para refletir o estado em que se encontravam naquele momento.

Um exemplo de uso de expressões de observação com pilhas de chamadas do asysnc

Avaliar o código de escopos anteriores

Além de simplesmente observar as expressões, é possível interagir com seu código de escopos anteriores diretamente no painel do Console JavaScript do DevTools.

Imagine que você é o Dr. Quem e precisa de uma ajudinha para comparar o relógio antes de entrar em Tardis com o "agora". No console do DevTools, é possível avaliar, armazenar e fazer cálculos com facilidade em valores de diferentes pontos de execução.

Um exemplo de como usar o Console JavaScript com pilhas de chamadas do ASC.
Use o Console JavaScript com pilhas de chamadas assíncronas para depurar seu código. A demonstração acima pode ser encontrada neste link.

Ficar no DevTools para manipular suas expressões vai evitar que você precise voltar ao código-fonte, fazer edições e atualizar o navegador.

Resolver resoluções de promessa encadeadas

Se você achava que o fluxo simulado anterior do Gmail era difícil de ser resolvido sem o recurso de pilha de chamadas assíncrona ativado, imagina como seria difícil lidar com fluxos assíncronos mais complexos, como promessas encadeadas? Vamos rever o exemplo final do tutorial de Jake Archibald sobre Promessas em JavaScript.

Veja uma pequena animação de apresentação das pilhas de chamadas no exemplo async-best-example.html de Jake.

Antes
Ponto de interrupção definido no exemplo de promessas sem pilhas de chamadas assíncronas
O painel "Call Stack" sem a ativação assíncrona.

Observe como o painel da pilha de chamadas tem poucas informações ao tentar depurar promessas.

Depois
Ponto de interrupção definido no exemplo de promessas com pilhas de chamadas assíncronas.
O painel "Call Stack" com o recurso assíncrono ativado.

Uau! Essas promessas. Muitos callbacks.

Receber insights sobre suas animações da Web

Vamos nos aprofundar nos arquivos do HTML5Rocks. Lembra-se do artigo Leaner, Meaner, Faster Animations with requestAnimationFrame de Paul Lewis?

Abra a demonstração do requestAnimationFrame e adicione um ponto de interrupção no início do método update() (perto da linha 874) de post.html. Com as pilhas de chamadas assíncronas, recebemos muito mais insights sobre o requestAnimationFrame, incluindo a capacidade de voltar até o callback do evento de rolagem inicial.

Antes
Ponto de interrupção definido no exemplo de requestAnimationFrame sem pilhas de chamadas assíncronas.
O painel "Call Stack" sem a ativação assíncrona.
Depois
Ponto de interrupção definido no exemplo de requestAnimationFrame com pilhas de chamadas assíncronas
E com a opção assíncrona ativada.

Rastrear atualizações do DOM ao usar o MutationObserver

Com MutationObserver, é possível observar as mudanças no DOM. Neste exemplo simples, quando você clica no botão, um novo nó DOM é anexado ao <div class="rows"></div>.

Adicione um ponto de interrupção em nodeAdded() (linha 31) em demo.html. Com as pilhas de chamadas assíncronas ativadas, é possível levar a pilha de chamadas de volta pelo addNode() até o evento de clique inicial.

Antes
Ponto de interrupção definido no exemplo de mutateObserver sem pilhas de chamadas assíncronas.
O painel "Call Stack" sem a ativação assíncrona.
Depois
Ponto de interrupção definido no exemplo de mutateObserver com pilhas de chamadas assíncronas.
E com a opção assíncrona ativada.

Dicas para depurar JavaScript em pilhas de chamadas assíncronas

Nomeie suas funções

Se você costuma atribuir todos os callbacks como funções anônimas, dê um nome a eles para facilitar a visualização da pilha de chamadas.

Por exemplo, considere uma função anônima como esta:

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

E dê um nome a ela, como windowLoaded():

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

Quando o evento de carregamento é disparado, ele aparece no stack trace do DevTools com o nome da função, em vez do código misterioso "(função anônima)". Isso facilita muito mais para você ver rapidamente o que está acontecendo no stack trace.

Antes
Uma função anônima.
Depois
Uma função nomeada

Mais informações

Recapitulando, esses são todos os callbacks assíncronos em que o DevTools exibirá toda a pilha de chamadas:

  • Timers: volte para onde setTimeout() ou setInterval() foi inicializado.
  • XHRs: volte para onde xhr.send() foi chamado.
  • Frames de animação: volte para onde requestAnimationFrame foi chamado.
  • Promessas: volta para onde uma promessa foi resolvida.
  • Object.observe: volte para onde o callback do observador foi originalmente vinculado.
  • MutationObservers: volta para onde o evento do observador de mutação foi disparado.
  • window.postMessage(): percorre as chamadas de mensagens durante o processo.
  • DataTransferItem.getAsString()
  • API FileSystem
  • IndexedDB
  • WebSQL
  • Eventos DOM qualificados via addEventListener(): volte para onde o evento foi disparado. Por motivos de desempenho, nem todos os eventos DOM estão qualificados para o recurso de pilhas de chamadas assíncronas. Exemplos de eventos disponíveis no momento incluem: "scroll", "hashchange" e "selectionchange"
  • Eventos multimídia via addEventListener(): volte para onde o evento foi disparado. Os eventos multimídia disponíveis incluem: eventos de áudio e vídeo (por exemplo, "play", "pause" e "ratechange"), eventos de WebRTC MediaStreamTrackList (por exemplo, "addtrack", "removetrack") e de MediaSource (por exemplo, "sourceopen").

A possibilidade de ver o stack trace completo dos callbacks de JavaScript é uma forma de ter você com isso. Esse recurso no DevTools será especialmente útil quando vários eventos assíncronos acontecerem em relação um ao outro ou se uma exceção não capturada for gerada em um callback assíncrono.

Faça um teste no Chrome. Se você tiver feedback sobre esse novo recurso, conte para a gente no bug Tracker do Chrome DevTools ou no Grupo do Chrome DevTools.