Introduzione
Quando si parla di gestione delle eccezioni in JavaScript, molti pensano subito agli errori sincroni: un accesso a una proprietà inesistente, una conversione fallita, una funzione che riceve un dato sbagliato. In realtà, uno degli aspetti più utili e spesso meno compresi riguarda le operazioni asincrone, soprattutto quando si usano async/await.
In questo tutorial vedremo come usare try...catch per intercettare errori in un flusso asincrono reale, ad esempio quando si recuperano dati da un’API e si devono gestire problemi di rete, risposte non valide o errori applicativi del server. È un caso pratico molto comune nello sviluppo frontend e backend con JavaScript.
L’obiettivo non è solo “evitare che l’app si rompa”, ma costruire un codice più affidabile, leggibile e facile da manutenere. Vedrai come distinguere gli errori attesi da quelli imprevisti, come organizzare il flusso logico e come scrivere un gestore riutilizzabile.
Codice completo
/**
* Esempio: recupero di un profilo utente da API con gestione errori asincroni.
* Scenario:
* - facciamo una richiesta HTTP
* - controlliamo lo status della risposta
* - gestiamo errori di rete, errori HTTP e dati inattesi
*/
async function loadUserProfile(userId) {
try {
// 1) Richiesta al server
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
// 2) Gestione errori HTTP: fetch non lancia errore per 404/500
if (!response.ok) {
throw new Error(`Errore HTTP: ${response.status} ${response.statusText}`);
}
// 3) Parsing dei dati ricevuti
const user = await response.json();
// 4) Validazione minima del contenuto
if (!user || !user.id || !user.name) {
throw new Error("Dati utente non validi ricevuti dal server");
}
// 5) Se tutto va bene, restituiamo un oggetto normalizzato
return {
id: user.id,
name: user.name,
email: user.email ?? "Email non disponibile",
company: user.company?.name ?? "Azienda non disponibile"
};
} catch (error) {
// Gestione centralizzata dell´errore
console.error("Impossibile caricare il profilo utente:", error.message);
// Possiamo rilanciare l´errore oppure restituire un fallback
return null;
}
}
// Esempio di utilizzo
(async () => {
const profile = await loadUserProfile(1);
if (profile) {
console.log("Profilo caricato con successo:", profile);
} else {
console.log("Mostra un messaggio all´utente: impossibile caricare i dati.");
}
})(); Spiegazione
Vediamo il codice passo per passo, perché il punto chiave non è solo “mettere tutto dentro un try”, ma capire cosa può fallire e come reagire in modo corretto.
1. try racchiude il flusso rischioso
All’interno del blocco try inseriamo le istruzioni che possono generare eccezioni: la chiamata fetch, il controllo della risposta, il parsing JSON e la validazione dei dati. In un flusso asincrono, il punto fondamentale è che await può “fermarsi” in attesa di una Promise e, se la Promise viene rifiutata, l’errore può essere intercettato dal catch.
2. fetch non intercetta automaticamente gli errori HTTP
Questo è un dettaglio molto importante. Se il server risponde con 404 o 500, fetch non va in errore di per sé: la Promise si risolve comunque, ma response.ok sarà false. Per questo motivo controlliamo manualmente lo stato della risposta e, se necessario, lanciamo un errore con throw new Error(...).
3. Il parsing e la validazione sono due cose diverse
Dopo aver ottenuto la risposta, usiamo response.json(). Anche questo passaggio può fallire se il contenuto non è JSON valido. Inoltre, anche quando il parsing riesce, i dati potrebbero non avere la struttura attesa. Per questo aggiungiamo una validazione minima: controlliamo che esistano id e name. In un progetto reale potresti usare validatori più robusti, ma il principio resta lo stesso.
4. catch centralizza la gestione dell’errore
Nel blocco catch raccogliamo tutti gli errori avvenuti nel flusso: problemi di rete, status HTTP non valido, JSON malformato, dati mancanti. Qui possiamo:
- mostrare un messaggio all’utente;
- scrivere un log tecnico in console o su un servizio esterno;
- restituire un valore di fallback, come null;
- rilanciare l’errore se deve essere gestito a un livello superiore.
5. Il chiamante decide come reagire
La funzione loadUserProfile restituisce null in caso di errore. Questo approccio è utile quando vuoi semplificare il codice chiamante. In alternativa, potresti lasciar propagare l’errore con throw e gestirlo in un altro punto dell’applicazione. La scelta dipende dal contesto: in UI spesso è utile avere un fallback, mentre nei servizi backend può essere preferibile propagare l’eccezione.
Best practice
Usare try...catch bene significa evitare sia il codice fragile sia il “catch tutto e basta”. Ecco alcune pratiche concrete da seguire.
- Metti nel try solo il codice realmente rischioso: non avvolgere intere funzioni senza motivo. Più il blocco è piccolo, più è chiaro cosa può fallire.
- Controlla sempre response.ok con fetch: altrimenti rischi di considerare “successo” una risposta 404 o 500.
- Usa errori descrittivi: quando lanci un’eccezione con throw new Error, scrivi un messaggio utile per il debugging.
- Non nascondere gli errori: evitare un catch vuoto è fondamentale. Se intercetti un errore, fai qualcosa di utile: log, fallback, notifica, retry.
- Distinguere errori attesi e inattesi: un timeout o un 404 possono essere gestiti con un messaggio amichevole; un errore di programmazione potrebbe richiedere il rilancio.
- Restituisci valori coerenti: se una funzione ritorna null in caso di errore, documentalo chiaramente e gestiscilo sempre nel chiamante.
- Valida i dati dopo il parsing: il fatto che il JSON sia valido non significa che contenga i campi necessari.
Un altro consiglio utile è separare la logica di accesso ai dati dalla logica di presentazione. In questo modo la funzione che usa try...catch si occupa solo del recupero e della validazione, mentre l’interfaccia decide come mostrare il risultato o l’errore.
Riepilogo
In questo tutorial abbiamo visto un uso molto pratico di try...catch: la gestione delle eccezioni durante le operazioni asincrone con async/await. Questo approccio è essenziale quando lavori con API, richieste di rete e dati esterni, perché gli errori possono arrivare in più punti del flusso.
I concetti fondamentali da ricordare sono:
- try contiene il codice che può fallire;
- catch intercetta gli errori generati nel flusso asincrono;
- fetch non considera errore gli status HTTP 4xx/5xx, quindi va controllato manualmente;
- il parsing dei dati e la loro validazione sono passaggi distinti;
- in caso di errore puoi loggare, restituire un fallback o rilanciare l’eccezione.
Se applichi questi principi, il tuo codice sarà più robusto e più facile da mantenere, soprattutto in applicazioni che dipendono da servizi esterni.
Approfondisci con risorse ufficiali
- MDN Web Docs - try...catch: documentazione completa sulla gestione delle eccezioni in JavaScript.
- MDN Web Docs - async function: approfondimento sul comportamento delle funzioni asincrone.
- MDN Web Docs - fetch(): guida ufficiale all’API Fetch e alla gestione delle risposte HTTP.
- ECMAScript Language Specification: riferimento formale per il comportamento di try...catch e delle Promise.
