Comment annuler une requête HTTP fetch ()?

201

Il existe une nouvelle API pour faire des requêtes à partir de JavaScript: fetch (). Existe-t-il un mécanisme intégré pour annuler ces demandes en vol?

Sam Lee
la source
peut se référer davidwalsh.name/cancel-fetch aussi
ganesh phirke

Réponses:

282

TL / DR:

fetchprend désormais en charge un signalparamètre au 20 septembre 2017, mais tous les navigateurs ne semblent pas le prendre en charge pour le moment .

MISE À JOUR 2020: La plupart des principaux navigateurs (Edge, Firefox, Chrome, Safari, Opera et quelques autres) prennent en charge la fonctionnalité , qui fait désormais partie du standard de vie DOM . (au 5 mars 2020)

C'est un changement que nous verrons très bientôt cependant, et vous devriez donc être en mesure d'annuler une demande en utilisant un AbortControllers AbortSignal.

Version longue

Comment:

Voici comment cela fonctionne:

Etape 1 : Vous créez un AbortController(Pour l' instant , je viens d' utiliser ce )

const controller = new AbortController()

Étape 2 : Vous obtenez le AbortControllersignal s comme ceci:

const signal = controller.signal

Étape 3 : Vous passez le signalpour récupérer comme ceci:

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

Étape 4 : abandonnez simplement chaque fois que vous devez:

controller.abort();

Voici un exemple de la façon dont cela fonctionnerait (fonctionne sur 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>

Sources:

  • La version finale d' AbortController a été ajoutée à la spécification DOM
  • Le PR correspondant pour la spécification d'extraction est maintenant fusionné.
  • Les bogues du navigateur qui suivent la mise en œuvre d'AbortController sont disponibles ici: Firefox: # 1378342 , Chrome: # 750599 , WebKit: # 174980 , Edge: # 13009916 .
SudoPlz
la source
2
Cette réponse est correcte et doit être votée positivement. Mais j'ai pris la liberté d'apporter quelques modifications à l'extrait de code, car en l'état il ne fonctionnait pas réellement dans Firefox 57+ - le shim semblait provoquer son échec ( «Err: TypeError: membre 'signal' de RequestInit n'implémente pas l'interface AbortSignal. " ) et il semble y avoir un problème avec le certificat pour slowwly.robertomurray.co.uk ( " Ce serveur n'a pas pu prouver qu'il est slowwly.robertomurray.co.uk; son certificat de sécurité est de * .herokuapp.com. " ), donc je l'ai changé pour utiliser simplement slowwly.robertomurray.co.uk (http simple).
Sideshowbarker
3
Mais maintenant, cela ne fonctionne pas sur d'autres navigateurs, c'est-à-dire Chrome parce que AbortController is not defined. Quoi qu'il en soit, ce n'est qu'une preuve de concept, au moins les gens avec Firefox 57+ peuvent le voir fonctionner
SudoPlz
3
C'est de l'or pur StackOverflow, merci pour l'écriture concise! Et les liens bugtracker aussi!
Kjellski
3
Maintenant, tous les navigateurs modernes le prennent en charge. developer.mozilla.org/en-US/docs/Web/API/AbortController/abort voir le tableau en bas
Alex Ivasyuv
2
Merci mais j'ai encore une question, devrions-nous changer le signal en vrai pour la prochaine extraction manuellement ??
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();

fonctionne dans 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), et plus tard.


sur les navigateurs plus anciens, vous pouvez utiliser le polyfill whatwg-fetch de github et le polyfill AbortController . vous pouvez détecter les anciens navigateurs et utiliser également les polyfills de manière conditionnelle :

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
la source
Si vous utilisez le polyfill fetch de github, cela est possible, suivez simplement les instructions sur leur fichier lisez
Fábio Santos
@ FábioSantos Votre commentaire devrait-il être sur la question, ou comme une réponse à part entière? Cela ne semble pas spécifique à ma réponse.
Jayen
Juste une note pour les personnes qui utilisent le polyfill github fetch. Je pensais que c'était pertinent pour votre réponse, car AFAIK, c'est le polyfill fetch le plus populaire disponible, et il remplit polyfonction la fonction que vous utilisez, fetch. Beaucoup de gens utiliseront ce polyfill à cause des anciens navigateurs. J'ai trouvé important de le mentionner parce que les gens supposent simplement que les polyfills réparent tout, mais celui-ci en particulier n'essaie pas de polyfill AbortController. Ils essaient d'utiliser AbortController en pensant que cela va être polyfilled dans les anciens navigateurs, et boom, il y a une exception dans un cas d'angle et uniquement sur les anciens navigateurs.
Fábio Santos
5

À partir de février 2018, fetch()peut être annulé avec le code ci-dessous sur Chrome (lire Utilisation de flux lisibles pour activer la prise en charge de Firefox). Aucune erreur n'est levée pour catch()le ramassage, et c'est une solution temporaire jusqu'à ce qu'elle AbortControllersoit complètement adoptée.

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
la source
2
Ce n'est PAS ce que le PO demandait. Ils veulent annuler la récupération et non le lecteur. La promesse de Fetch ne se résout qu'après la fin de la demande, ce qui est trop tard pour annuler la demande au serveur.
Rahly
3

Pour l'instant, il n'y a pas de solution appropriée, comme le dit @spro.

Cependant, si vous avez une réponse en vol et utilisez ReadableStream, vous pouvez fermer le flux pour annuler la demande.

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();
});
Brad
la source
0

Faisons 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;
}

N'oubliez pas que le code n'est pas testé! Faites-moi savoir si vous l'avez testé et que quelque chose n'a pas fonctionné. Il peut vous avertir que vous essayez d'écraser la fonction «récupérer» de la bibliothèque officielle JavaScript.

0xC0DEGURU
la source