Wie kann ich eine HTTP-fetch () -Anforderung abbrechen?

200

Es gibt eine neue API für Anfragen aus JavaScript: fetch (). Gibt es einen eingebauten Mechanismus zum Stornieren dieser Anfragen während des Flugs?

Sam Lee
quelle
beziehen davidwalsh.name/cancel-fetch auch
ganesh phirke

Antworten:

280

TL / DR:

fetchunterstützt jetzt einen signalParameter ab dem 20. September 2017, aber derzeit scheinen dies nicht alle Browser zu unterstützen .

2020 UPDATE: Die meisten gängigen Browser (Edge, Firefox, Chrome, Safari, Opera und einige andere) unterstützen die Funktion , die Teil des DOM-Lebensstandards geworden ist . (Stand 5. März 2020)

Dies ist eine Änderung, die wir jedoch sehr bald sehen werden. Daher sollten Sie in der Lage sein, eine Anfrage mit einem AbortControllers abzubrechen AbortSignal.

Lange Version

Wie man:

So funktioniert es:

Schritt 1 : Sie erstellen ein AbortController(Vorerst ich nur verwendet diese )

const controller = new AbortController()

Schritt 2 : Sie erhalten das AbortControllers-Signal wie folgt:

const signal = controller.signal

Schritt 3 : Sie übergeben das signalzu holen wie folgt:

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

Schritt 4 : Brechen Sie einfach ab, wann immer Sie müssen:

controller.abort();

Hier ist ein Beispiel, wie es funktionieren würde (funktioniert unter 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>

Quellen:

SudoPlz
quelle
2
Diese Antwort ist richtig und sollte positiv bewertet werden. Ich habe mir jedoch die Freiheit genommen, einige Änderungen am Code-Snippet vorzunehmen, da es in Firefox 57+ so wie es ist nicht funktioniert hat - das Shim schien dazu zu führen, dass es fehlschlug ( „Err: TypeError: 'signal' -Mitglied von RequestInit) implementiert die Schnittstelle AbortSignal nicht. ” ) und es scheint ein Problem mit dem Zertifikat für slowwly.robertomurray.co.uk zu geben ( „ Dieser Server konnte nicht beweisen, dass es sich um slowwly.robertomurray.co.uk handelt; sein Sicherheitszertifikat stammt von * .herokuapp.com. ” ), also habe ich es geändert, um nur slowwly.robertomurray.co.uk (einfach http) zu verwenden.
Sideshowbarker
3
Aber jetzt funktioniert es nicht mit anderen Browsern, dh Chrome, weil AbortController is not defined. Auf jeden Fall ist dies nur ein Proof of Concept, zumindest Leute mit Firefox 57+ können sehen, dass es funktioniert
SudoPlz
3
Dies ist reines StackOverflow-Gold, danke für die kurze Beschreibung! Und der Bugtracker verlinkt auch!
Kjellski
3
Jetzt unterstützen es alle modernen Browser. developer.mozilla.org/en-US/docs/Web/API/AbortController/abort siehe Tabelle unten
Alex Ivasyuv
2
Danke, aber ich habe noch eine Frage, sollten wir das Signal für den nächsten Abruf manuell wieder auf true ändern?
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();

funktioniert 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) und später.


In älteren Browsern können Sie die whatwg-fetch-Polyfüllung von github und die AbortController- Polyfüllung verwenden . Sie können ältere Browser erkennen und die Polyfills auch bedingt verwenden :

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
quelle
Wenn Sie Githubs Fetch Polyfill verwenden, ist dies möglich. Befolgen Sie einfach die Anweisungen in der Readme- Datei
Fábio Santos
@ FábioSantos Sollte Ihr Kommentar zu der Frage oder als eigenständige Antwort sein? Es sieht nicht spezifisch für meine Antwort aus.
Jayen
Nur ein Hinweis für Leute, die die Github Fetch Polyfill verwenden. Ich dachte, es wäre relevant für Ihre Antwort, weil AFAIK die beliebteste verfügbare Abruf-Polyfüllung ist und die von Ihnen verwendete Funktion, Abrufen, mehrfach ausfüllt. Viele Leute werden diese Polyfüllung wegen alter Browser verwenden. Ich fand es wichtig zu erwähnen, weil die Leute einfach davon ausgehen, dass Polyfills alles reparieren, aber dieser versucht nicht, AbortController zu polyfillen. Sie würden versuchen, AbortController zu verwenden, weil sie glauben, dass es in alten Browsern mehrfach gefüllt sein wird, und Boom, es gibt eine Ausnahme in einem Eckfall und nur in alten Browsern.
Fábio Santos
5

Stand Februar 2018, fetch() kann mit dem folgenden Code in Chrome gekündigt werden (siehe Verwenden lesbarer Streams , um die Firefox-Unterstützung zu aktivieren). Es wird kein Fehler catch()zum Aufnehmen ausgegeben, und dies ist eine vorübergehende Lösung, bis sie AbortControllervollständig übernommen ist.

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
quelle
2
Dies ist NICHT das, wonach das OP gefragt hat. Sie wollen den Abruf abbrechen, nicht den Leser. Das Versprechen von Fetch wird erst nach Abschluss der Anforderung aufgelöst. Dies ist zu spät, um die Anforderung an den Server abzubrechen.
Rahly
3

Derzeit gibt es keine richtige Lösung, wie @spro sagt.

Wenn Sie jedoch eine Antwort während des Flugs haben und ReadableStream verwenden, können Sie den Stream schließen, um die Anforderung abzubrechen.

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
quelle
0

Lassen Sie uns 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;
}

Bitte beachten Sie, dass der Code nicht getestet wird! Lassen Sie mich wissen, wenn Sie es getestet haben und etwas nicht funktioniert hat. Möglicherweise erhalten Sie Warnungen, dass Sie versuchen, die Funktion "Abrufen" aus der offiziellen JavaScript-Bibliothek zu überschreiben.

0xC0DEGURU
quelle