JavaScript Promises: come usare <strong>.catch()</strong> per gestire i fallimenti nelle catene di <strong>.th

by Anastasia P.
SHARE
JavaScript Promises: come usare <strong>.catch()</strong> per gestire i fallimenti nelle catene di <strong>.th
© Guida-HTML5.it

Introduzione

Le Promises in JavaScript sono uno strumento fondamentale per lavorare con operazioni asincrone come richieste HTTP, lettura di file, timer o chiamate a servizi esterni. Una Promise può avere tre stati: pending, fulfilled e rejected. Quando qualcosa va storto, la Promise viene rifiutata e l’errore può essere intercettato con .catch().

In questo tutorial vediamo un aspetto molto pratico e spesso sottovalutato: come centralizzare la gestione degli errori in fondo a una catena di Promise. Questo approccio è utile quando hai più passaggi consecutivi e vuoi evitare di ripetere lo stesso blocco di gestione errori in ogni singolo .then().

L’idea è semplice: ogni passaggio della catena può trasformare il dato ricevuto, validarlo o arricchirlo; se uno di questi passaggi fallisce, l’errore “salta” automaticamente fino al primo .catch() disponibile.

Codice completo

// Simuliamo un flusso asincrono in più passaggi:
// 1) recupero di un profilo utente
// 2) validazione dei dati
// 3) caricamento dei preferiti
// 4) generazione di un riepilogo finale

function fetchUserProfile(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId !== 42) {
        reject(new Error("Utente non trovato"));
        return;
      }

      resolve({
        id: 42,
        name: "Luca",
        role: "admin"
      });
    }, 400);
  });
}

function validateProfile(profile) {
  if (!profile.name) {
    throw new Error("Nome utente mancante");
  }

  if (profile.role !== "admin") {
    throw new Error("Permessi insufficienti");
  }

  return profile;
}

function loadFavorites(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId === 42) {
        resolve(["JavaScript", "Node.js", "TypeScript"]);
      } else {
        reject(new Error("Impossibile caricare i preferiti"));
      }
    }, 300);
  });
}

function buildSummary(profile, favorites) {
  return {
    message: `Ciao ${profile.name}, hai ${favorites.length} preferiti salvati.`,
    items: favorites
  };
}

// Catena di Promise con gestione centralizzata degli errori
fetchUserProfile(42)
  .then((profile) => {
    console.log("Profilo caricato:", profile);
    return validateProfile(profile);
  })
  .then((validProfile) => {
    return loadFavorites(validProfile.id).then((favorites) => {
      return buildSummary(validProfile, favorites);
    });
  })
  .then((summary) => {
    console.log("Riepilogo finale:", summary.message);
    console.log("Lista preferiti:", summary.items);
  })
  .catch((error) => {
    console.error("Si è verificato un errore nel flusso:", error.message);
  });

// Esempio alternativo: errore nella validazione
fetchUserProfile(42)
  .then((profile) => {
    // Simuliamo un problema nei dati
    profile.role = "guest";
    return validateProfile(profile);
  })
  .then((validProfile) => {
    return loadFavorites(validProfile.id);
  })
  .then((favorites) => {
    console.log("Preferiti:", favorites);
  })
  .catch((error) => {
    console.error("Errore intercettato da catch:", error.message);
  });

Spiegazione

Nel primo esempio partiamo da fetchUserProfile(42), che restituisce una Promise. Se l’ID è corretto, la Promise si risolve con un oggetto utente. Da lì iniziano i vari .then(), ognuno con un compito preciso.

1) Ogni .then() può trasformare il dato

Il primo .then() riceve il profilo e lo passa a validateProfile(). Questa funzione non restituisce una Promise, ma può comunque lanciare un errore con throw. Se succede, la catena viene interrotta e il controllo passa direttamente al .catch() finale.

2) Gli errori “saltano” fino al primo .catch()

Questo è uno dei vantaggi più importanti delle Promise: non devi controllare manualmente ogni singolo passaggio con if o try/catch annidati. Se una Promise viene rifiutata, oppure se una funzione dentro un .then() genera un’eccezione, il sistema la tratta come un errore della catena.

3) Anche Promise annidate possono propagare errori

Nel secondo .then() abbiamo una Promise annidata:

loadFavorites(validProfile.id).then(...)

In questo caso, se loadFavorites() fallisce, il rifiuto viene propagato verso l’esterno e intercettato dal .catch() finale. Però, per leggibilità, spesso è meglio evitare annidamenti inutili e restituire direttamente la Promise, così la catena resta più lineare.

Una versione più pulita sarebbe questa:

fetchUserProfile(42)
  .then((profile) => {
    return validateProfile(profile);
  })
  .then((validProfile) => {
    return loadFavorites(validProfile.id);
  })
  .then((favorites) => {
    const summary = buildSummary({ name: "Luca" }, favorites);
    console.log(summary.message);
  })
  .catch((error) => {
    console.error("Errore:", error.message);
  });

In questo caso il flusso è più facile da seguire e la gestione dell’errore resta centralizzata.

4) .catch() non serve solo per reject espliciti

Molti pensano che .catch() intercetti solo i reject() delle Promise. In realtà cattura anche:

  • eccezioni lanciate con throw dentro un .then();
  • errori generati da funzioni chiamate nella catena;
  • Promise restituite e poi rifiutate nei passaggi successivi.

Questo rende .catch() un punto unico e coerente dove registrare errori, mostrare messaggi all’utente o fare fallback.

Best practice

  • Metti un solo .catch() finale quando vuoi gestire tutti gli errori della catena in un punto centrale.
  • Restituisci sempre le Promise dentro i .then() per mantenere il flusso leggibile e prevedibile.
  • Evita annidamenti inutili: se una funzione restituisce già una Promise, non creare ulteriori livelli non necessari.
  • Lancia errori chiari con messaggi descrittivi, ad esempio "Permessi insufficienti" invece di messaggi vaghi.
  • Non nascondere gli errori: nel .catch() fai logging, mostra un messaggio utile o propaga l’errore se serve.
  • Separa validazione e accesso ai dati: funzioni piccole e specializzate rendono più facile capire dove nasce il problema.

Un errore comune è scrivere catene molto lunghe senza una strategia chiara. In questi casi, il .catch() finale è utile, ma il codice deve comunque rimanere leggibile. Se la logica cresce troppo, conviene dividere il flusso in funzioni più piccole.

Riepilogo

Usare .catch() in fondo a una catena di Promise è una soluzione elegante e pratica per gestire gli errori in modo centralizzato. È particolarmente utile quando il flusso asincrono include più trasformazioni, validazioni o operazioni dipendenti tra loro.

I punti chiave da ricordare sono:

  • ogni .then() può restituire un valore, una Promise o lanciare un errore;
  • un errore in qualsiasi passaggio arriva al .catch() più vicino;
  • centralizzare la gestione degli errori migliora la leggibilità e riduce la duplicazione;
  • catene ben strutturate sono più facili da testare e da mantenere.

Se lavori spesso con API, autenticazione o flussi di caricamento dati, questa tecnica ti aiuterà a scrivere codice più robusto e professionale.

Approfondisci con risorse ufficiali

  • MDN Web Docs - Promise: documentazione completa su stati, metodi e comportamento delle Promise.
  • MDN Web Docs - Promise.prototype.catch(): approfondimento specifico sul metodo .catch().
  • MDN Web Docs - Error: utile per capire come creare e gestire errori personalizzati.
  • ECMAScript Language Specification: riferimento ufficiale per il comportamento del linguaggio JavaScript.

SHARE