Como sabemos fetch devuelve una promesa. Y generalmente JavaScript no tiene un concepto de “abortar” una promesa. Entonces, ¿cómo podemos abortar una llamada al método fetch? Por ejemplo si las acciones del usuario en nuestro sitio indican que fetch no se necesitará más.
Existe para esto de forma nativa un objeto especial: AbortController. Puede ser utilizado para abortar no solo fetch sino otras tareas asincrónicas también.
Su uso es muy sencillo:
El objeto AbortController
Crear un controlador:
let controller = new AbortController();
Este controlador es un objeto extremadamente simple.
- Tiene un único método
abort(), - y una única propiedad
signalque permite establecerle escuchadores de eventos.
Cuando abort() es invocado:
controller.signalemite el evento"abort".- La propiedad
controller.signal.abortedtoma el valortrue.
Generalmente tenemos dos partes en el proceso:
- El que ejecuta la operación de cancelación, genera un listener que escucha a
controller.signal. - El que cancela: este llama a
controller.abort()cuando es necesario.
Tal como se muestra a continuación (por ahora sin fetch):
let controller = new AbortController();
let signal = controller.signal;
// La parte que ejecuta la operación de cancelación
// obtiene el objeto "signal"
// y genera un listener que se dispara cuando es llamado controller.abort()
signal.addEventListener('abort', () => alert("abort!"));
// El que cancela (más tarde en cualquier punto):
controller.abort(); // abort!
// El evento se dispara y signal.aborted se vuelve true
alert(signal.aborted); // true
Como podemos ver, AbortController es simplemente la via para pasar eventos abort cuando abort() es llamado sobre él.
Podríamos implementar alguna clase de escucha de evento en nuestro código por nuestra cuenta, sin el objeto AbortController en absoluto.
Pero lo valioso es que fetch sabe cómo trabajar con el objeto AbortController, está integrado con él.
Uso con fetch
Para posibilitar la cancelación de fetch, pasa la propiedad signal de un AbortController como una opción de fetch:
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});
El método fetch conoce cómo trabajar con AbortController. Este escuchará eventos abort sobre signal.
Ahora, para abortar, llamamos controller.abort():
controller.abort();
Terminamos: fetch obtiene el evento desde signal y aborta el requerimiento.
Cuando un fetch es abortado, su promesa es rechazada con un error AbortError, así podemos manejarlo, por ejemplo en try..catch.
Aquí hay un ejemplo completo con fetch abortado después de 1 segundo:
// Se abortara en un segundo
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') { // se maneja el abort()
alert("Aborted!");
} else {
throw err;
}
}
AbortController es escalable
AbortController es escalable, permite cancelar múltiples fetch de una vez.
Aquí hay un bosquejo de código que de muchos fetch de url en paralelo, y usa un simple controlador para abortarlos a todos:
let urls = [...]; // una lista de urls para utilizar fetch en paralelo
let controller = new AbortController();
// un array de promesas fetch
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
// si controller.abort() es llamado,
// se abortaran todas las solicitudes fetch
En el caso de tener nuestras propias tareas asincrónicas aparte de fetch, podemos utilizar un único AbortController para detenerlas junto con fetch.
Solo es necesario escuchar el evento abort en nuestras tareas:
let urls = [...];
let controller = new AbortController();
let ourJob = new Promise((resolve, reject) => { // nuestra tarea
...
controller.signal.addEventListener('abort', reject);
});
let fetchJobs = urls.map(url => fetch(url, { // varios fetch
signal: controller.signal
}));
// Se espera por la finalización de los fetch y nuestra tarea
let results = await Promise.all([...fetchJobs, ourJob]);
// en caso de que se llame al método controller.abort() desde algún sitio,
// se abortan todos los fetch y nuestra tarea.
Resumen
AbortControlleres un simple objeto que genera un eventoabortsobre su propiedadsignalcuando el métodoabort()es llamado (y también establecesignal.abortedentrue).fetchestá integrado con él: pasamos la propiedadsignalcomo opción, y entoncesfetchla escucha, así se vuelve posible abortarfetch.- Podemos usar
AbortControlleren nuestro código. La interacción “llamarabort()” → “escuchar eventoabort” es simple y universal. Podemos usarla incluso sinfetch.
Comentarios
<code>, para varias líneas – envolverlas en la etiqueta<pre>, para más de 10 líneas – utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen…)