JavaScript Promise: usare <strong>.catch()</strong> per gestire timeout e fallimenti di rete

by Anastasia P.
SHARE
JavaScript Promise: usare <strong>.catch()</strong> per gestire timeout e fallimenti di rete
© Guida-HTML5.it

Introduzione

Quando lavoriamo con le Promise in JavaScript, tendiamo a concentrarci sul flusso “felice”: richiesta inviata, risposta ricevuta, dati elaborati. Nella pratica, però, le cose che si rompono più spesso sono due: la rete e il tempo di attesa. Una richiesta può fallire perché il server non risponde, la connessione cade, oppure perché l’operazione impiega troppo e vogliamo interromperla.

In questo tutorial vedremo un sotto-argomento molto utile e concreto: come usare .catch() per gestire timeout e errori di rete in una Promise. È un caso reale, frequente nelle app front-end e nei servizi Node.js, e ci aiuta a scrivere codice più robusto e leggibile.

L’idea è semplice: creiamo una funzione che simula una chiamata remota, la avvolgiamo in una Promise, aggiungiamo un timeout e intercettiamo i problemi con .catch(). In questo modo possiamo distinguere tra un errore applicativo, un errore di rete e una scadenza temporale.

Codice completo

// Simula una richiesta asincrona verso un server
function fakeApiRequest() {
  return new Promise((resolve, reject) => {
    const networkDelay = Math.floor(Math.random() * 4000) + 500; // 500ms - 4500ms
    const networkCanFail = Math.random() < 0.3; // 30% di probabilità di errore

    setTimeout(() => {
      if (networkCanFail) {
        reject(new Error("Errore di rete: impossibile raggiungere il server"));
        return;
      }

      resolve({
        status: 200,
        data: {
          id: 42,
          name: "Prodotto demo",
          price: 19.99
        },
        delay: networkDelay
      });
    }, networkDelay);
  });
}

// Crea un timeout che fallisce dopo un certo numero di millisecondi
function timeoutPromise(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`Timeout: la richiesta ha superato ${ms} ms`));
    }, ms);
  });
}

// Esegue la richiesta e fallisce se il timeout scade prima della risposta
function requestWithTimeout(timeoutMs) {
  return Promise.race([
    fakeApiRequest(),
    timeoutPromise(timeoutMs)
  ]);
}

// Uso pratico con .catch()
requestWithTimeout(2500)
  .then((response) => {
    console.log("Richiesta completata con successo");
    console.log("Dati ricevuti:", response);
  })
  .catch((error) => {
    if (error.message.startsWith("Timeout")) {
      console.error("Gestione timeout:", error.message);
    } else {
      console.error("Gestione errore di rete:", error.message);
    }
  })
  .finally(() => {
    console.log("Operazione terminata: successo o errore");
  });

Spiegazione

Questo esempio è costruito per mostrare un problema molto comune: una richiesta asincrona che può riuscire, fallire oppure restare troppo a lungo in sospeso.

1. fakeApiRequest()

Questa funzione restituisce una Promise che simula una chiamata a un’API. Dopo un ritardo casuale:

  • nel 30% dei casi chiama reject() con un errore di rete;
  • negli altri casi chiama resolve() con un oggetto dati.

È un ottimo modo per testare il comportamento del codice senza dipendere da un server reale.

2. timeoutPromise(ms)

Questa funzione crea una Promise che fallisce dopo un certo numero di millisecondi. Non restituisce dati: serve solo a generare un errore se il tempo massimo viene superato.

Questo approccio è molto utile quando vuoi evitare che l’interfaccia resti bloccata in attesa troppo a lungo.

3. Promise.race()

Promise.race() restituisce il risultato della prima Promise che si risolve o si rifiuta. Nel nostro caso, “vince”:

  • la risposta dell’API, se arriva prima;
  • oppure il timeout, se scade prima.

Questa è una soluzione pratica per implementare un limite di attesa senza complicare troppo il codice.

4. .catch()

Qui entra in gioco la gestione degli errori. Con .catch() intercettiamo sia l’errore di rete sia il timeout. Nel blocco di errore possiamo:

  • mostrare un messaggio diverso per ciascun caso;
  • registrare l’errore nei log;
  • attivare un fallback, ad esempio dati cache o un messaggio “riprovare più tardi”.

Nel codice facciamo un controllo semplice sul contenuto del messaggio:

  • se inizia con Timeout, gestiamo il problema come scadenza temporale;
  • altrimenti lo trattiamo come errore di rete.

5. .finally()

Anche se il tema principale è .catch(), vale la pena notare .finally(): viene eseguito sia in caso di successo sia in caso di errore. È perfetto per spegnere loader, rimuovere spinner o liberare risorse.

Best practice

Quando gestisci errori con .catch() in scenari di rete e timeout, alcune regole ti aiutano a scrivere codice più solido:

  • Non usare messaggi generici: meglio distinguere timeout, errore di rete e errore di validazione.
  • Centralizza la gestione degli errori quando possibile, soprattutto in applicazioni con molte richieste.
  • Evita di nascondere gli errori: un .catch() vuoto rende il debug molto difficile.
  • Usa Promise.race() con attenzione: il timeout non annulla davvero l’operazione sottostante, la interrompe solo a livello logico. In contesti reali, con fetch, conviene usare anche AbortController.
  • Restituisci sempre una Promise coerente quando costruisci utility personalizzate, così il comportamento resta prevedibile.

Un errore frequente è fare affidamento solo su .then() e dimenticare che una Promise può fallire in qualsiasi punto della catena. .catch() deve essere pensato come una rete di sicurezza, non come un’aggiunta opzionale.

Se lavori con richieste HTTP reali, una buona evoluzione di questo pattern è integrare fetch con un timeout e un abort esplicito. In quel caso, .catch() diventa il punto unico in cui intercettare sia gli errori di rete sia l’interruzione forzata.

Riepilogo

In questo tutorial abbiamo visto come usare .catch() per gestire in modo pratico due problemi molto comuni nelle Promise: timeout ed errori di rete.

  • Abbiamo creato una Promise che simula una chiamata API.
  • Abbiamo aggiunto una Promise dedicata al timeout.
  • Abbiamo usato Promise.race() per prendere il primo evento utile.
  • Abbiamo gestito i fallimenti con .catch() in modo differenziato.

Il vantaggio di questo approccio è duplice: il codice resta leggibile e la gestione degli errori diventa più precisa. In applicazioni reali, questo significa interfacce più affidabili e utenti meno confusi quando qualcosa va storto.

Approfondisci con risorse ufficiali

  • MDN Web Docs - Promise: documentazione completa sul comportamento delle Promise in JavaScript.
  • MDN Web Docs - Promise.race(): utile per capire come funziona la competizione tra più Promise.
  • MDN Web Docs - Error: per approfondire la struttura degli oggetti errore in JavaScript.
  • MDN Web Docs - Fetch API: perfetta per applicare questi concetti alle richieste HTTP reali.
  • MDN Web Docs - AbortController: risorsa fondamentale per interrompere richieste asincrone in modo corretto.

SHARE