Come posso annullare una richiesta fetch () HTTP?


Risposte:


282

TL / DR:

fetchora supporta un signalparametro dal 20 settembre 2017, ma al momento non tutti i browser sembrano supportarlo .

AGGIORNAMENTO 2020: la maggior parte dei principali browser (Edge, Firefox, Chrome, Safari, Opera e pochi altri) supportano la funzione , che è diventata parte dello standard di vita DOM . (a partire dal 5 marzo 2020)

Questa è una modifica che vedremo molto presto, quindi dovresti essere in grado di annullare una richiesta usando una AbortControllers AbortSignal.

Versione lunga

Come:

Il modo in cui funziona è questo:

Fase 1 : Si crea un AbortController(per ora ho appena usato questo )

const controller = new AbortController()

Passaggio 2 : ricevi il AbortControllersegnale s in questo modo:

const signal = controller.signal

Passaggio 3 : passi il signalper recuperare in questo modo:

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

Passaggio 4 : basta interrompere ogni volta che è necessario:

controller.abort();

Ecco un esempio di come funzionerebbe (funziona su 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>

fonti:


2
Questa risposta è corretta e dovrebbe essere votata. Ma mi sono preso la libertà di apportare alcune modifiche allo snippet di codice, perché così com'è in realtà non funzionava in Firefox 57+ - lo shim sembrava causare un errore ( “Err: TypeError: membro 'signal' di RequestInit non implementa l'interfaccia AbortSignal. ” ) e sembra esserci qualche problema con il certificato per slowwly.robertomurray.co.uk ( “ Questo server non ha potuto dimostrare che è slowwly.robertomurray.co.uk; il suo certificato di sicurezza proviene da * .herokuapp.com. ” ), quindi l'ho cambiato per usare solo slowwly.robertomurray.co.uk (semplice http).
sideshowbarker,

3
Ma ora non funziona su altri browser, ad esempio Chrome perché AbortController is not defined. Comunque questa è solo una prova di concetto, almeno le persone con Firefox 57+ possono vederlo funzionare
SudoPlz

3
Questo è puro oro StackOverflow, grazie per il conciso writeup! E anche i bugtracker si collegano!
Kjellski,

3
Ora tutti i browser moderni lo supportano. developer.mozilla.org/en-US/docs/Web/API/AbortController/abort vedi tabella in fondo
Alex Ivasyuv

2
Grazie ma ho ancora una domanda, dovremmo cambiare il segnale in vero per il prossimo recupero 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();

funziona in 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) e versioni successive.


sui browser meno recenti, è possibile utilizzare il polyfill whatwg-fetch di github e il polyfill AbortController . puoi rilevare i browser più vecchi e utilizzare i polyfill anche in modo condizionale :

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

Se si utilizza il polyfill di recupero di github, questo è possibile a che fare con esso, basta seguire le istruzioni sul loro readme: github.com/github/fetch#aborting-requests
Fábio Santos

@ FábioSantos Il tuo commento dovrebbe essere sulla domanda o come risposta a sé stante? Non sembra specifico alla mia risposta.
Jayen,

Solo una nota per le persone che stanno usando il polyfill di recupero github. Ho pensato che fosse pertinente per la tua risposta perché AFAIK è il polifill di recupero più popolare disponibile e polifilla la funzione che stai utilizzando, recupera. Molte persone useranno questo polyfill a causa dei vecchi browser. Ho trovato importante menzionarlo perché le persone assumono semplicemente che i polyfill risolvano tutto, ma questo particolare non tenta di eseguire il polyfill di AbortController. Avrebbero provato a usare AbortController pensando che sarebbe stato riempito di poligoni nei vecchi browser, e boom, c'è un'eccezione in un caso d'angolo e solo su vecchi browser.
Fábio Santos,

5

A partire da febbraio 2018, fetch()può essere annullato con il codice riportato di seguito su Chrome (leggi Utilizzo dei flussi leggibili per abilitare il supporto di Firefox). Non viene generato alcun errore per la catch()raccolta, e questa è una soluzione temporanea fino a quando non AbortControllerviene completamente adottata.

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();
        })
      }
    }
  }
}

2
Questo NON è ciò che l'OP stava chiedendo. Vogliono annullare il recupero non il lettore. La promessa di Fetch non si risolve fino al termine della richiesta, che è troppo tardi per annullare la richiesta al server.
Rahly,

3

Per ora non esiste una soluzione adeguata, come dice @spro.

Tuttavia, se si dispone di una risposta in volo e si utilizza ReadableStream, è possibile chiudere lo stream per annullare la richiesta.

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();
});

0

Let's 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;
}

Tieni presente che il codice non è stato testato! Fammi sapere se l'hai provato e qualcosa non ha funzionato. Potrebbe darti avvisi che provi a sovrascrivere la funzione 'fetch' dalla libreria ufficiale JavaScript.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.