How do I cancel an HTTP fetch() request?

前端 未结 6 1611
小蘑菇
小蘑菇 2020-11-22 14:58

There is a new API for making requests from JavaScript: fetch(). Is there any built in mechanism for canceling these requests in-flight?

相关标签:
6条回答
  • 2020-11-22 15:23

    As of Feb 2018, fetch() can be cancelled with the code below on Chrome (read Using Readable Streams to enable Firefox support). No error is thrown for catch() to pick up, and this is a temporary solution until AbortController is fully adopted.

    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();
            })
          }
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-22 15:33

    This works in browser and nodejs Live browser demo

    const cpFetch= require('cp-fetch');
    const url= 'https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s';
     
    const chain = cpFetch(url, {timeout: 10000})
        .then(response => response.json())
        .then(data => console.log(`Done: `, data), err => console.log(`Error: `, err))
     
    setTimeout(()=> chain.cancel(), 1000); // abort the request after 1000ms 
    
    0 讨论(0)
  • 2020-11-22 15:40

    TL/DR:

    fetch now supports a signal parameter as of 20 September 2017, but not all browsers seem support this at the moment.

    2020 UPDATE: Most major browsers (Edge, Firefox, Chrome, Safari, Opera, and a few others) support the feature, which has become part of the DOM living standard. (as of 5 March 2020)

    This is a change we will be seeing very soon though, and so you should be able to cancel a request by using an AbortControllers AbortSignal.

    Long Version

    How to:

    The way it works is this:

    Step 1: You create an AbortController (For now I just used this)

    const controller = new AbortController()

    Step 2: You get the AbortControllers signal like this:

    const signal = controller.signal

    Step 3: You pass the signal to fetch like so:

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

    Step 4: Just abort whenever you need to:

    controller.abort();

    Here's an example of how it would work (works on 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:

    • The final version of AbortController has been added to the DOM specification
    • The corresponding PR for the fetch specification is now merged.
    • Browser bugs tracking the implementation of AbortController is available here: Firefox: #1378342, Chromium: #750599, WebKit: #174980, Edge: #13009916.
    0 讨论(0)
  • 2020-11-22 15:41

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

    works 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), and later.


    on older browsers, you can use github's whatwg-fetch polyfill and AbortController polyfill. you can detect older browsers and use the polyfills conditionally, too:

    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
    
    0 讨论(0)
  • 2020-11-22 15:46

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

    Please have in mind that the code is not tested! Let me know if you have tested it and something didn't work. It may give you warnings that you try to overwrite the 'fetch' function from the JavaScript official library.

    0 讨论(0)
  • 2020-11-22 15:48

    As for now there is no proper solution, as @spro says.

    However, if you have an in-flight response and are using ReadableStream, you can close the stream to cancel the request.

    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 讨论(0)
提交回复
热议问题