¿Cómo cancelo una solicitud HTTP fetch ()?

Respuestas:

282

TL / DR:

fetch ahora soporta un signal parámetro a partir del 20 de septiembre de 2017, pero no todos los navegadores parecen admitirlo en este momento .

ACTUALIZACIÓN 2020: la mayoría de los principales navegadores (Edge, Firefox, Chrome, Safari, Opera y algunos otros) admiten la función , que se ha convertido en parte del estándar de vida DOM . (a partir del 5 de marzo de 2020)

Sin embargo, este es un cambio que veremos muy pronto, por lo que debería poder cancelar una solicitud utilizando un AbortController s AbortSignal.

Versión larga

Cómo:

La forma en que funciona es la siguiente:

Paso 1 : creas un AbortController(Por ahora acabo de usar esto )

const controller = new AbortController()

Paso 2 : obtienes la AbortControllerseñal s como esta:

const signal = controller.signal

Paso 3 : Pasas la signalbúsqueda de la siguiente manera:

fetch(urlToFetch, {
    method: 'get',
    signal: signal, // <------ This is our AbortSignal
})

Paso 4 : simplemente aborta cada vez que necesites:

controller.abort();

Aquí hay un ejemplo de cómo funcionaría (funciona en Firefox 57+):

<script>
    // Create an instance.
    const controller = new AbortController()
    const signal = controller.signal

    /*
    // Register a listenr.
    signal.addEventListener("abort", () => {
        console.log("aborted!")
    })
    */


    function beginFetching() {
        console.log('Now fetching');
        var urlToFetch = "https://httpbin.org/delay/3";

        fetch(urlToFetch, {
                method: 'get',
                signal: signal,
            })
            .then(function(response) {
                console.log(`Fetch complete. (Not aborted)`);
            }).catch(function(err) {
                console.error(` Err: ${err}`);
            });
    }


    function abortFetching() {
        console.log('Now aborting');
        // Abort.
        controller.abort()
    }

</script>



<h1>Example of fetch abort</h1>
<hr>
<button onclick="beginFetching();">
    Begin
</button>
<button onclick="abortFetching();">
    Abort
</button>

Fuentes:

SudoPlz
fuente
2
Esta respuesta es correcta y debe ser votada. Pero me tomé la libertad de hacer algunas ediciones en el fragmento de código, porque tal como estaba en realidad no funcionaba en Firefox 57+, la cuña parecía estar causando que fallara ( "Err: TypeError: miembro de" señal "de RequestInit no implementa la interfaz AbortSignal ". ) y parece haber algún problema con el certificado para slowwly.robertomurray.co.uk ( " Este servidor no pudo probar que es slowwly.robertomurray.co.uk; su certificado de seguridad es de * .herokuapp.com. " ), así que lo cambié para usar slowwly.robertomurray.co.uk (http simple).
sidehowbarker
3
Pero ahora no funciona en otros navegadores, es decir, Chrome porque AbortController is not defined. De todos modos, esto es solo una prueba de concepto, al menos las personas con Firefox 57+ pueden verlo funcionando
SudoPlz
3
Esto es oro puro de StackOverflow, ¡gracias por la concisa redacción! ¡Y los enlaces de bugtracker también!
Kjellski
3
Ahora todos los navegadores modernos lo admiten. developer.mozilla.org/en-US/docs/Web/API/AbortController/abort ver tabla en la parte inferior
Alex Ivasyuv
2
Gracias, pero todavía tengo una pregunta, ¿deberíamos cambiar la señal de nuevo a verdadero para la próxima búsqueda manualmente?
akshay kishore
20

https://developers.google.com/web/updates/2017/09/abortable-fetch

https://dom.spec.whatwg.org/#aborting-ongoing-activities

// setup AbortController
const controller = new AbortController();
// signal to pass to fetch
const signal = controller.signal;

// fetch as usual
fetch(url, { signal }).then(response => {
  ...
}).catch(e => {
  // catch the abort if you like
  if (e.name === 'AbortError') {
    ...
  }
});

// when you want to abort
controller.abort();

funciona en edge 16 (2017-10-17), firefox 57 (2017-11-14), desktop safari 11.1 (2018-03-29), ios safari 11.4 (2018-03-29), chrome 67 (2018-05 -29), y luego.


en navegadores antiguos, puede usar el polyfill whatwg-fetch de github y el polyfill AbortController . puede detectar navegadores antiguos y usar los polyfills condicionalmente también:

import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
import {fetch} from 'whatwg-fetch'

// use native browser implementation if it supports aborting
const abortableFetch = ('signal' in new Request('')) ? window.fetch : fetch
Jayen
fuente
Si usa el polyfill de búsqueda de github, esto es posible hacerlo, solo siga las instrucciones en su archivo
Fábio Santos
@ FábioSantos ¿Debería su comentario ser sobre la pregunta, o como respuesta en sí misma? No parece específico para mi respuesta.
Jayen
Solo una nota para las personas que usan el polyfill de Github Fetch. Pensé que era relevante para su respuesta porque AFAIK es el polyfill de recuperación más popular disponible, y llena la función que está utilizando, recuperar. Mucha gente usará este polyfill debido a los antiguos navegadores. Me pareció importante mencionarlo porque la gente simplemente asume que los polyfills arreglan todo, pero este en particular no intenta polyfill AbortController. Intentarían usar AbortController pensando que se va a rellenar en navegadores antiguos, y boom, hay una excepción en un caso de esquina y solo en navegadores antiguos.
Fábio Santos
5

A partir de febrero de 2018, fetch()se puede cancelar con el siguiente código en Chrome (lea Uso de transmisiones legibles para habilitar el soporte de Firefox). No se produce ningún error para catch()recoger, y esta es una solución temporal hasta que AbortControllerse adopte por completo.

fetch('YOUR_CUSTOM_URL')
.then(response => {
  if (!response.body) {
    console.warn("ReadableStream is not yet supported in this browser.  See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream")
    return response;
  }

  // get reference to ReadableStream so we can cancel/abort this fetch request.
  const responseReader = response.body.getReader();
  startAbortSimulation(responseReader);

  // Return a new Response object that implements a custom reader.
  return new Response(new ReadableStream(new ReadableStreamConfig(responseReader)));
})
.then(response => response.blob())
.then(data => console.log('Download ended. Bytes downloaded:', data.size))
.catch(error => console.error('Error during fetch()', error))


// Here's an example of how to abort request once fetch() starts
function startAbortSimulation(responseReader) {
  // abort fetch() after 50ms
  setTimeout(function() {
    console.log('aborting fetch()...');
    responseReader.cancel()
    .then(function() {
      console.log('fetch() aborted');
    })
  },50)
}


// ReadableStream constructor requires custom implementation of start() method
function ReadableStreamConfig(reader) {
  return {
    start(controller) {
      read();
      function read() {
        reader.read().then(({done,value}) => {
          if (done) {
            controller.close();
            return;
          }
          controller.enqueue(value);
          read();
        })
      }
    }
  }
}
AnthumChris
fuente
2
Esto NO es lo que pedía el OP. Quieren cancelar la búsqueda, no el lector. La promesa de Fetch no se resuelve hasta DESPUÉS de que la solicitud haya finalizado, que es demasiado tarde para cancelar la solicitud al servidor.
Rahly
3

Por ahora no hay una solución adecuada, como dice @spro.

Sin embargo, si tiene una respuesta en vuelo y está utilizando ReadableStream, puede cerrar la transmisión para cancelar la solicitud.

fetch('http://example.com').then((res) => {
  const reader = res.body.getReader();

  /*
   * Your code for reading streams goes here
   */

  // To abort/cancel HTTP request...
  reader.cancel();
});
Puntilla
fuente
0

Polyfill:

if(!AbortController){
  class AbortController {
    constructor() {
      this.aborted = false;
      this.signal = this.signal.bind(this);
    }
    signal(abortFn, scope) {
      if (this.aborted) {
        abortFn.apply(scope, { name: 'AbortError' });
        this.aborted = false;
      } else {
        this.abortFn = abortFn.bind(scope);
      }
    }
    abort() {
      if (this.abortFn) {
        this.abortFn({ reason: 'canceled' });
        this.aborted = false;
      } else {
        this.aborted = true;
      }
    }
  }

  const originalFetch = window.fetch;

  const customFetch = (url, options) => {
    const { signal } = options || {};

    return new Promise((resolve, reject) => {
      if (signal) {
        signal(reject, this);
      }
      originalFetch(url, options)
        .then(resolve)
        .catch(reject);
    });
  };

  window.fetch = customFetch;
}

¡Tenga en cuenta que el código no se prueba! Avíseme si lo ha probado y algo no funcionó. Puede darle advertencias de que intenta sobrescribir la función 'buscar' de la biblioteca oficial de JavaScript.

0xC0DEGURU
fuente