Come debuggo gli errori async/await in TypeScript?

Sto lavorando a un'API Node.js con TypeScript e da qualche settimana ho problemi con async/await. Le Promise non si risolvono correttamente in qualche modo e ricevo messaggi d'errore criptici che non mi aiutano molto. Come si procede al meglio quando si deve debuggare?

5 risposte

★ Miglior risposta

Il problema di solito non sta in async/await in sé, ma nel fatto che non vedi gli errori correttamente - perché finiscono nello scope sbagliato o perché una Promise in background viene silenziosamente rejected.

Mi è successo di recente: avevo una funzione che faceva più API call in parallelo, e una ha fallito. All'inizio ho pensato che il mio codice con await fosse rotto, ma alla fine era semplicemente che non avevo loggato l'errore. Perciò: metti `.catch()` dappertutto, anche per le Promise che "non ti servono", e logga in modo aggressivo con `console.error()` o un Logger - dai all'errore il contesto completo. Questo aiuta molto più di leggere Stack Trace. E controlla sempre se usi Promise.all() o Promise.race() da qualche parte - anche quelli possono fregarti se una Promise fallisce in silenzio.

I messaggi di errore "criptici" nella maggior parte dei casi non sono poi così criptici - devi solo sapere dove guardare. Prova a scrivere le tue funzioni async con `.catch()` espliciti invece di usare solo try-catch, e stampa tutto lo stack dell'errore, non solo il messaggio. Usa anche il Node.js Debugger o Chrome DevTools per passare step by step attraverso le tue Promise - allora vedrai subito dove una Promise si blocca o non viene risolta affatto. Un errore frequente è anche dimenticarsi di `await` oppure avviare accidentalmente Promise senza `await`; con `eslint-plugin-promise` catturi automaticamente questo tipo di cose.

Nel debug di async/await in TypeScript, lo stack trace è il tuo migliore amico - ma devi saperlo leggere correttamente. Se una Promise rimane irrisolta, controlla prima se da qualche parte ti sei dimenticato l'`await`. Succede più spesso di quanto pensi: chiami una funzione async, ma non aspetti il risultato. Anche con `.map()` o `.forEach()` spesso va male, perché i callback sono async, ma la map stessa non aspetta. Qui aiutano `Promise.all()` o `for...of` invece dei metodi Array.

Un altro punto importante: guarda la tua strategia di error handling. Se hai solo try-catch e il messaggio di errore continua a sembrare criptico, registra l'intero oggetto Error, non solo il messaggio. `console.log(error)` invece di `console.log(error.message)` spesso mostra molto più contesto. E molto importante - in Node.js ci sono unhandled Promise Rejections che semplicemente muoiono in background. Attiva il listener-event `process.on('unhandledRejection', ...)` per il debug, così vedi dove le promise davvero falliscono.

Un consiglio pratico: usa TypeScript stesso come strumento di debug. Se le tue type annotations sono corrette, il compilatore TypeScript ti dirà già se ti sei dimenticato di awaitar - ricevi errori di tipo invece di sorprese a runtime. Ti fa risparmiare tempo di debug.

un trucco pratico che uso: registra l'ora e il contesto prima e dopo ogni await. così noti subito dove si blocca qualcosa o impiega un'eternità a caricarsi. `console.time('myFunc')` prima della funzione async, poi `console.timeEnd('myFunc')` dopo. sembra semplice, ma spesso ti mostra subito dov'è il collo di bottiglia - a volte sono dipendenze che impiegano una vita, non il tuo codice.

ancora più importante: usa `Promise.all()` o `Promise.allSettled()` quando fai girare più promise in parallelo. `allSettled` è oro colato, perché ti permette di vedere quali promise falliscono e quali no - invece che una muoia per prima e il resto venga ignorato. con `allSettled` ricevi un oggetto per ogni promise con status e value/reason, così vedi esattamente dove si blocca.

e un hack che molti si perdono: aggiungi nel tuo tsconfig.json `"lib": ["es2020"]` (o più recente) e usa correttamente optional chaining e nullish coalescing sui risultati delle promise. ma realisticamente - scrivi anche qualche statement `debugger;` direttamente nelle funzioni async e avvia node con `--inspect`. poi apri chrome://inspect e debugga in tempo reale. lì vedi lo stack di chiamate completo e puoi controllare passo dopo passo dove si bloccano le promise. è purtroppo più laborioso dei log, ma quando i log non ti aiutano, è la mossa vincente.

Scordati tutti i blocchi `.catch()` accanto a try-catch, solo rende tutto più confuso - rimani fedele a una strategia. Quello che funziona davvero: attiva gli eventi `unhandledRejection` nella tua app Node (`process.on('unhandledRejection', ...)`) e logga tutto lì, altrimenti le tue Promise muoiono in silenzio. Poi scrivi temporaneamente un semplice utility wrapper che avvolge ogni funzione async con logging - prima dell'await annoti timestamp e funzione, dopo di nuovo, e vedi subito se qualcosa si blocca o impiega troppo tempo. `console.time()` funziona, ma è troppo granulare per funzioni grandi; qualche `console.log('prima dell'await X')` posizionato strategicamente è utile all'inizio.

La tua risposta

Accediper rispondere.