È noto che fetch
ritorna una prime. JavaScript comunemente non ha però il concetto di “interruzione” di una promise. Come possiamo fare quindi ad interrompere una chiamata fetch
?
C’è uno speciale operatore built-in per questo scopo: AbortController
, il quale può essere usato per interrompere fetch
ed anche altri tasks asincroni.
L’utilizzo è molto semplice:
- Step 1: Creare un controller:
Create a controller:
let controller = new AbortController();
Un controller è un oggetto estremamente semplice.
- Possiede un solo metodo
abort()
, - Ed un’unica proprietà
signal
che permette di impostare dei controllori di evento (event listeneres).
Quando viene invocato il metodo abort()
:
controller.signal
emette l’evento"abort"
.- la proprietà
controller.signal.aborted
diventatrue
.
Generalmente il processo si suddivide in due parti:
- quella che esegue l’operazione annullabile, imposta il listener su
controller.signal
. - quella che annulla: essa chiama
controller.abort()
quando necessario.
Qui vediamo un esempio completo (ancora senza fetch
):
let controller = new AbortController();
let signal = controller.signal;
// La parte che esegue un'operazione annullabile
// ottiene l'oggetto "signal"
// ed imposta il controllore di eventi di attivarsi quando viene invocato controller.abort()
signal.addEventListener('abort', () => alert("abort!"));
// L'altra parte, che cancella (in un qualsiasi punto più avanti):
controller.abort(); // abort!
// L'evento si innesca e il segnale di aborted diventa true
alert(signal.aborted); // true
Potremmo implementare lo stesso tipo controllo di eventi nel nostro codice, anche senza l’oggetto AbortController
.
Ma ciò che dà valore è che fetch
è ottimizzato per lavorare con l’oggetto AbortController
, poiché è integrato.
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});
Il metodo fetch
è in grado di lavorare con AbortController
. Starà in attesa dell’event abort
su signal
.
Ora, per abortire la richiesta, invochiamo controller.abort()
:
controller.abort();
Abbiamo finito: fetch
riceve l’event da signal
ed interrompe la richiesta.
Quando un fetch viene interrotto, la promise viene rifiutata con un errore AbortError
, quindi dovremmo prevedere una sua gestione, e.g. all’interno di un try..catch
.
Qui vediamo l’esempio completo con fetch
, che viene interrotto dopo 1 secondo:
// interrompi in 1 secondo
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // gestisci l'abort()
alert("Aborted!");
} else {
throw err;
}
}
AbortController è scalabile
AbortController
è scalabile e può cancellare fetches multipli in una volta.
Ad esempio, qui valutiamo molti url
in parallelo e il controller li interrompe tutti:
let urls = [...]; // un elenco di urls di cui fare il fetch in parallelo
let controller = new AbortController();
// un array di fetch promises
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
// se controller.abort() è chiamato da qualche parte del codice
// saranno interrotte tutte le fetches
Se abbiamo dei jobs asincroni, oltre a fetch
, possiamo usare un singoloAbortController
per fermarli insieme alle fetches.
Dobbiamo solo ascoltare il suo evento abort
:
let urls = [...];
let controller = new AbortController();
let ourJob = new Promise((resolve, reject) => { // un nostro task
...
controller.signal.addEventListener('abort', reject);
});
let fetchJobs = urls.map(url => fetch(url, { // fetches
signal: controller.signal
}));
// Attende per le and ed il nostro task in parallelo
let results = await Promise.all([...fetchJobs, ourJob]);
// se controller.abort() è chiamato da qualche parte del codice
// saranno interrotte tutte le fetches e ourJob
Riepilogo
AbortController
è un semplice oggetto che genera eventi diabort
sulla proprietàsignal
quando viene invocato il metodoabort()
(ed imposta anchesignal.aborted
atrue
).fetch
si integra con esso: se passiamo la proprietàsignal
come opzione,fetch
è in grado di controllarla, quindi sarà possibile interrompere l’operazione difetch
.- Possiamo utilizzare
AbortController
nel nostro codice. La chiamataabort()
" → "ascolterà l’eventoabort
". Possiamo utilizzarla anche senzafetch
.