Gestire le eccezioni in JavaScript con <strong>try...catch</strong> durante le operazioni asincrone

by Anastasia P.
SHARE
Gestire le eccezioni in JavaScript con <strong>try...catch</strong> durante le operazioni asincrone
© Guida-HTML5.it

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.

SHARE