Como debugo erros de async/await em TypeScript?

Christina Schmidt DE 🔍 Entusiasta 👁 92 ⚑ Denunciar Programação

Estou trabalhando em uma API Node.js com TypeScript e tenho tido problemas com async/await há algumas semanas. As Promises não estão sendo resolvidas direito de alguma forma e recebo mensagens de erro criptografadas que realmente não me ajudam. Como é que se debuga isso da melhor forma?

5 respostas

★ Melhor resposta

O problema geralmente não está no async/await em si, mas no fato de você não estar vendo os erros direito - porque eles caem no escopo errado ou porque uma Promise no background é rejeitada em silêncio.

Aconteceu comigo recentemente: tinha uma função que fazia vários API calls em paralelo, e um deles falhou. A princípio achei que meu código com await estava quebrado, mas no final era simplesmente que eu não estava logando o erro. Por isso: coloca `.catch()` em todo lugar, até em Promises que você "não precisa", e loga agressivamente com `console.error()` ou um Logger - dá ao erro o contexto completo. Isso ajuda muito mais do que ler Stack Traces. E sempre verifica se você tá usando Promise.all() ou Promise.race() em algum lugar - elas também podem te enganar se uma Promise falha silenciosamente.

As mensagens de erro "criptografadas" na maioria das vezes não são tão criptografadas assim - você só precisa saber onde procurar. Escreve tuas funções async com blocos `.catch()` explícitos em vez de apenas try-catch, e loga o stack de erro completo, não só a mensagem. Usa também o Node.js Debugger ou Chrome DevTools para passar por tuas Promises passo a passo - aí você vê na hora onde uma Promise trava ou simplesmente nunca é resolvida. Um erro bem comum também é esquecer `await` ou acidentalmente iniciar Promises sem `await`; com `eslint-plugin-promise` você pega essas coisas automaticamente.

Ao fazer debug de async/await em TypeScript, o stack trace é seu melhor amigo - mas você precisa saber lê-lo direito. Se uma Promise fica pendurada sem resolver, primeiro checa se você não esqueceu de `await` em algum lugar. Isso acontece mais frequentemente do que se pensa: você chama uma função async, mas não espera pelo resultado. Também com `.map()` ou `.forEach()` costuma dar ruim, porque os callbacks são async, mas o próprio map não espera. Aqui ajuda usar `Promise.all()` ou `for...of` em vez de métodos de array.

Outro ponto importante: dá uma olhada na sua estratégia de tratamento de erros. Se você só tem try-catch e a mensagem de erro ainda parece criptografada, faça log do objeto de erro completo, não só da mensagem. `console.log(error)` em vez de `console.log(error.message)` geralmente mostra muito mais contexto. E bem importante - em Node.js tem Promise Rejections não tratadas que simplesmente morrem em background. Ativa o listener-event `process.on('unhandledRejection', ...)` para debugar, aí você vê onde as promises realmente falham.

Uma dica prática: usa o próprio TypeScript como ferramenta de debug. Se suas type annotations estão corretas, o compilador TypeScript já vai te avisar se você esqueceu de awaitar - você recebe erros de tipo em vez de surpresas em runtime. Isso economiza seu tempo de debug.

um truque prático que uso: registra a hora e o contexto antes e depois de cada await. assim tu vê logo onde as coisas travam ou demoram eternamente. `console.time('myFunc')` antes da função async, depois `console.timeEnd('myFunc')` depois. parece simples, mas geralmente mostra o bottleneck na hora - às vezes são dependências que demoram pra caramba, não o teu código.

ainda mais importante: usa `Promise.all()` ou `Promise.allSettled()` quando tu tá rodando várias promises em paralelo. `allSettled` é ouro puro, porque tu vê quais promises falharam e quais não - em vez de uma morrer cedo e o resto ser ignorado. com `allSettled` tu ganha um objeto pra cada promise com status e value/reason de volta, aí tu vê exatamente onde tá travado.

e um hack que muita gente ignora: adiciona `"lib": ["es2020"]` (ou mais novo) no teu tsconfig.json e usa optional chaining & nullish coalescing direito nos resultados de promises. mas real talk - escreve também `debugger;` statements direto nas funções async e roda node com `--inspect`. aí tu abre chrome://inspect e debugga ao vivo. lá tu vê a stack completa e consegue acompanhar passo a passo onde as promises travam. é mais trabalhoso que logs, mas quando os logs não te ajudam, esse é o caminho.

Esqueça todos os blocos `.catch()` junto com try-catch, isso só torna as coisas mais confusas - mantenha uma única estratégia. O que realmente ajuda: ative eventos `unhandledRejection` na sua app Node (`process.on('unhandledRejection', ...)`) e registre tudo lá, senão suas Promises morrem em silêncio. Depois escreva temporariamente um simples utilitário wrapper que envolva cada função async com logging - antes do await você anota timestamp e função, depois novamente, e você vê na hora se algo está travado ou demorando demais. O `console.time()` funciona, mas é muito granular para funções grandes; alguns `console.log('antes do await X')` estrategicamente posicionados são úteis no começo.

Sua resposta

Entrarpara responder.