Como depurar JavaScript assíncrono com o Chrome DevTools

Introdução

Um recurso poderoso que torna o JavaScript único é a capacidade de trabalhar de forma assíncrona usando funções de callback. A atribuição de callbacks assíncronos permite escrever código orientado a eventos, mas também torna o rastreamento de bugs uma experiência frustrante, já que o JavaScript não é executado de maneira linear.

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

Uma visão geral rápida das pilhas de chamadas assíncronas.
Visão geral rápida das pilhas de chamadas assíncronas. Vamos detalhar o fluxo desta demonstração em breve.

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

Ao percorrer o stack trace, você também pode analisar o valor de qualquer variável nesse ponto específico da execução do ambiente de execução. É como uma máquina do tempo para expressões de relógio!

Vamos ativar esse recurso e conferir alguns desses cenários.

Ativar a depuração assíncrona no Chrome

Teste esse novo recurso ativando-o no Chrome. Acesse o painel Sources das Chrome DevTools do Canary.

Ao lado do painel Call Stack no lado direito, há uma nova caixa de seleção para "Async". Marque ou desmarque a caixa de seleção para ativar ou desativar a depuração assíncrona. No entanto, depois de ativada, talvez você não queira desativar essa opção.

Ative ou desative o recurso assíncrono.

Capturar eventos de timer atrasados e respostas XHR

Você provavelmente já viu isso no Gmail:

O Gmail tentando enviar um e-mail.

Se houver um problema ao enviar a solicitação (o servidor está com problemas ou há problemas de conectividade de rede no lado do cliente), o Gmail vai tentar reenviar a mensagem automaticamente após um breve tempo limite.

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

Fluxograma de exemplo de Gmail falso.
No diagrama acima, os métodos destacados em azul são os melhores lugares para que esse novo recurso do DevTool seja o mais benéfico, já que esses métodos funcionam de forma assíncrona.

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

Antes
Ponto de interrupção definido no exemplo de Gmail simulado sem pilhas de chamada assíncrona.
O painel da pilha de chamadas sem ativar o modo assíncrono.

Aqui, você pode conferir que postOnFail() foi iniciado por um callback AJAX, mas sem mais informações.

Depois
Ponto de interrupção definido no exemplo de Gmail simulado com pilhas de chamada assíncrona.
O painel Call Stack com a opção assíncrona ativada.

Aqui, você pode conferir que a XHR foi iniciada em submitHandler(). Legal!

Com as pilhas de chamadas assíncronas ativadas, é possível conferir toda a pilha de chamadas para saber se a solicitação foi iniciada em submitHandler() (o que acontece depois de clicar no botão de envio) ou em retrySubmit() (o que acontece após um atraso de setTimeout()):

submitHandler()
Ponto de interrupção definido no exemplo de Gmail falso com pilhas de chamadas assíncronas
retrySubmit()
Outro ponto de interrupção definido no exemplo de Gmail simulado com pilhas de chamadas assíncronas

Observar expressões de forma assíncrona

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

Exemplo de uso de expressões de observação com pilhas de chamada assíncrona

Avaliar o código de escopos anteriores

Além de simplesmente monitorar expressões, você pode interagir com o código de escopos anteriores no painel do console JavaScript do DevTools.

Imagine que você é o Dr. Who e precisa de uma ajudinha para comparar o relógio de antes de entrar no 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.

Exemplo de uso do console JavaScript com pilhas de chamadas assíncronas.
Use o console JavaScript com pilhas de chamadas assíncronas para depurar seu código. Confira a demonstração acima neste link.

Ficar no DevTools para manipular as expressões economiza tempo, porque você não precisa voltar ao código-fonte, fazer edições e atualizar o navegador.

Desfazer resoluções de promessas encadeadas

Se você achou que o fluxo de Gmail simulado anterior era difícil de desvendar sem o recurso de pilha de chamadas assíncrona ativado, imagine o quanto seria mais difícil com fluxos assíncronos mais complexos, como promessas conectadas. Vamos revisitar o exemplo final do tutorial de Jake Archibald sobre promessas do JavaScript.

Confira uma pequena animação de como percorrer as pilhas de chamadas no exemplo async-best-example.html de Jake.

Antes
Exemplo de ponto de interrupção definido em promessas sem pilhas de chamadas assíncronas
O painel da pilha de chamadas sem ativar o modo assíncrono.

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 a opção assíncrona ativada.

Uau! Essas promessas. Muitas callbacks.

Receba insights sobre suas animações da Web

Vamos nos aprofundar nos arquivos do HTML5Rocks. Lembra do artigo de Paul Lewis Animações mais simples, mais rápidas e mais eficientes com requestAnimationFrame?

Abra a demonstração de requestAnimationFrame e adicione um ponto de interrupção no início do método update() (por volta da linha 874) de post.html. Com as pilhas de chamadas assíncronas, temos muito mais insights sobre 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 da pilha de chamadas sem ativar o modo assíncrono.
Depois
Ponto de interrupção definido no exemplo de requestAnimationFrame com pilhas de chamadas assíncronas
E com a função assíncrona ativada.

Rastrear atualizações do DOM ao usar MutationObserver

Os MutationObserver permitem observar mudanças no DOM. Neste exemplo simples, quando você clica no botão, um novo nó DOM é anexado a <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, agora é possível percorrer a pilha de chamadas de volta por addNode() até o evento de clique inicial.

Antes
Ponto de interrupção definido no exemplo de mutationObserver sem pilhas de chamada assíncrona.
O painel da pilha de chamadas sem ativar o modo assíncrono.
Depois
Ponto de interrupção definido no exemplo de mutationObserver com pilhas de chamadas assíncronas.
E com a função assíncrona ativada.

Dicas para depurar JavaScript em pilhas de chamadas assíncronas

Nomear as funções

Se você tende a atribuir todos os callbacks como funções anônimas, é recomendável dar 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 como windowLoaded():

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

Quando o evento de carregamento é acionado, ele aparece no stack trace do DevTools com o nome da função, em vez do "(função anônima)". Isso facilita a visualização rápida do que está acontecendo no stack trace.

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

Mais informações

Para recapitular, estes são todos os callbacks assíncronos em que o DevTools vai mostrar a pilha de chamadas completa:

  • 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: volte para onde uma promessa foi resolvida.
  • Object.observe: volte para onde o callback do observador foi vinculado originalmente.
  • MutationObservers: volte para onde o evento do observador de mutação foi acionado.
  • window.postMessage(): analise as chamadas de mensagens intraprocessuais.
  • DataTransferItem.getAsString()
  • API FileSystem
  • IndexedDB
  • WebSQL
  • Eventos DOM qualificados por addEventListener(): volte para onde o evento foi acionado. Por motivos de desempenho, nem todos os eventos do DOM estão qualificados para o recurso de pilhas de chamadas assíncronas. Exemplos de eventos disponíveis atualmente incluem: "scroll", "hashchange" e "selectionchange".
  • Eventos multimídia por addEventListener(): volte para onde o evento foi acionado. Os eventos multimídia disponíveis incluem: eventos de áudio e vídeo (por exemplo, "play", "pause", "ratechange"), eventos MediaStreamTrackList do WebRTC (por exemplo, "addtrack", "removetrack") e eventos MediaSource (por exemplo, "sourceopen").

Ter acesso ao stack trace completo dos seus callbacks JavaScript vai manter seus cabelos. Esse recurso nas Ferramentas do desenvolvedor será especialmente útil quando vários eventos assíncronos ocorrerem em relação a outros ou se uma exceção não detectada for gerada em um callback assíncrono.

Teste no Chrome. Se você tiver feedback sobre esse novo recurso, envie uma mensagem para o rastreador de bugs do Chrome DevTools ou para o grupo do Chrome DevTools.