# AbortController: come annullare una fetch in JavaScript

> Come usare AbortController e AbortSignal per annullare richieste fetch e altre operazioni asincrone, evitando race condition e sprechi di rete.

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-08-29
- URL: https://diegobetto.com/abortcontroller-fetch-javascript/
- Tag: javascript, async, fetch

Una casella di ricerca con autocomplete che chiama un'API a ogni carattere digitato è l'esempio da manuale, ma il problema è più generale di così: qualunque `fetch` che puoi lanciare più volte prima che la precedente sia finita — un filtro che cambia, una tab che l'utente riapre, un componente che si smonta mentre la richiesta è ancora in volo — ha lo stesso bug latente. Le risposte possono arrivare in un ordine diverso da quello in cui sono partite, e se aggiorni lo stato con l'ultima risposta _ricevuta_ invece che con l'ultima _richiesta_, l'utente vede per un istante il risultato sbagliato.

`AbortController` esiste esattamente per questo: dà a JavaScript un modo standard per dire a un'operazione asincrona già avviata "non mi interessa più il tuo risultato, fermati".

## Il problema, concretamente

```javascript
async function cercaProdotti(query) {
  const res = await fetch(`/api/search?q=${query}`);
  const data = await res.json();
  renderRisultati(data); // quale richiesta vince?
}

input.addEventListener("input", (e) => cercaProdotti(e.target.value));
```

Digita "mel", poi subito dopo "mela". Partono due fetch. Se la richiesta per "mel" — magari perché il server è momentaneamente più lento su quella query specifica — risponde _dopo_ quella per "mela", `renderRisultati` viene chiamata due volte e l'ultima chiamata vince: sullo schermo restano i risultati di "mel", anche se l'utente ha già digitato "mela" da un pezzo. Non è un errore che lanci un'eccezione, è un bug puramente visivo, silenzioso, difficile da riprodurre in modo affidabile — il tipo di bug che finisce ignorato per mesi perché "a volte capita, non si sa perché".

## La soluzione: un controller per richiesta

```javascript
let controllerAttuale = null;

async function cercaProdotti(query) {
  controllerAttuale?.abort(); // annulla la richiesta precedente, se c'è
  controllerAttuale = new AbortController();

  try {
    const res = await fetch(`/api/search?q=${query}`, {
      signal: controllerAttuale.signal,
    });
    const data = await res.json();
    renderRisultati(data);
  } catch (err) {
    if (err.name === "AbortError") return; // annullata di proposito, non un errore reale
    console.error("Ricerca fallita", err);
  }
}
```

Ogni nuova chiamata annulla il controller della chiamata precedente prima di crearne uno nuovo. Quando `abort()` viene chiamato, `fetch` rigetta la sua promise con un `AbortError` — per questo il blocco `catch` lo riconosce esplicitamente e semplicemente esce, senza trattarlo come un errore di rete vero.

> `fetch()` rigetta, non risolve, quando viene annullata: un errore comune è dimenticare che
> `AbortError` passa dal `catch`, non da un controllo dentro il blocco `try`. Se il tuo codice non
> distingue `AbortError` dagli altri errori di rete, finisci per mostrare un messaggio "Errore di
> connessione" all'utente ogni volta che semplicemente digita un carattere in più — un bug
> fastidioso quanto quello che stavi cercando di risolvere.

## Non solo `fetch`: qualunque API che accetta un signal

`AbortSignal` non è specifico di `fetch` — è diventato un pattern standard che molte API web nativas accettano:

```javascript
// Un event listener che si rimuove da solo
document.addEventListener("click", handler, { signal: controller.signal });

// Un timer annullabile senza salvare l'id con clearTimeout
setTimeout(() => console.log("eseguito"), 1000 /* non nativamente, serve un wrapper */);
```

Per `setTimeout` non c'è supporto nativo diretto al signal, ma il pattern si integra facilmente:

```javascript
function attendiCancellabile(ms, signal) {
  return new Promise((resolve, reject) => {
    const id = setTimeout(resolve, ms);
    signal.addEventListener("abort", () => {
      clearTimeout(id);
      reject(new DOMException("Annullato", "AbortError"));
    });
  });
}
```

Lo stesso `AbortController` può quindi annullare più operazioni contemporaneamente collegate allo stesso `signal` — utile quando un singolo evento (l'utente naviga via, un componente si smonta) deve fermare più cose in una volta sola, senza tenere traccia manualmente di ognuna.

## Cancellazione automatica con un timeout

Un uso pratico che vale la pena conoscere: `AbortSignal.timeout()` crea un signal che si annulla da solo dopo N millisecondi, utile per non lasciare mai una fetch appesa indefinitamente su una rete lenta o un server che non risponde:

```javascript
const res = await fetch("/api/dati-lenti", {
  signal: AbortSignal.timeout(5000), // annulla automaticamente dopo 5 secondi
});
```

Puoi anche combinare più signal con `AbortSignal.any()` — la richiesta si annulla non appena **uno qualsiasi** dei signal forniti scatta, utile per unire "l'utente ha annullato manualmente" e "è scaduto il timeout" nella stessa fetch senza gestirli separatamente:

```javascript
const signal = AbortSignal.any([controllerUtente.signal, AbortSignal.timeout(5000)]);
```

## Il legame con debounce e throttle

Se il problema che stai risolvendo è "l'utente digita troppo velocemente e genero troppe richieste", `AbortController` e il [debounce](https://diegobetto.com/debounce-throttle-javascript/) non sono alternative, sono complementari e risolvono due problemi diversi: il debounce riduce **quante** richieste partono nel tempo (aspetta una pausa nella digitazione prima di chiamare l'API); `AbortController` garantisce che, delle richieste che partono comunque, solo l'ultima conti davvero. Un campo di ricerca fatto bene, in pratica, quasi sempre li usa insieme.

> Non serve solo per fetch di rete: il pattern vale ogni volta che hai un'operazione asincrona il
> cui risultato può diventare irrilevante prima di completarsi — inclusi casi non di rete, come un
> `IntersectionObserver` collegato a un componente che si smonta, o una chiamata
> `stopPropagation`/`preventDefault` su un evento che potrebbe non arrivare mai se l'elemento viene
> rimosso prima. Se già gestisci la{" "}
> [propagazione degli eventi](https://diegobetto.com/javascript-stoppropagation-preventdefault/) con
> attenzione, `AbortSignal` è lo stesso tipo di disciplina applicata al codice asincrono invece che
> agli eventi del DOM.
