【问题标题】:How do I cancel an HTTP fetch() request?如何取消 HTTP fetch() 请求?
【发布时间】:2015-09-12 17:43:02
【问题描述】:

有一个用于从 JavaScript 发出请求的新 API:fetch()。是否有任何内置机制可以在进行中取消这些请求?

【问题讨论】:

标签: javascript ajax fetch-api


【解决方案1】:

简单的打字版本(获取中止):

export async function fetchWithTimeout(url: RequestInfo, options?: RequestInit, timeout?: number) {
    return new Promise<Response>((resolve, reject) => {
        const controller = new AbortController();
        const signal = controller.signal;

        const timeoutId = setTimeout(() => {
            console.log('TIMEOUT');
            reject('Timeout');
            // Cancel fetch in progress
            controller.abort();
        }, timeout ?? 5 * 1000);

        fetch(url, { ...options, signal })
            .then((response) => {
                clearTimeout(timeoutId);
                resolve(response);
            })
            .catch((e) => reject(e));
    });
}

也许你需要一个 polyfill(例如 IE11):

https://polyfill.io/v3/polyfill.min.js?features=AbortController

【讨论】:

    【解决方案2】:

    这适用于浏览器和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 
    

    【讨论】:

      【解决方案3】:

      让我们来填充:

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

      请记住,代码未经测试!让我知道您是否已经对其进行了测试并且有些东西不起作用。它可能会警告您尝试覆盖 JavaScript 官方库中的 'fetch' 函数。

      【讨论】:

        【解决方案4】:

        TL/DR:

        fetch 现在支持 signal 参数,截至 2017 年 9 月 20 日,但 不 目前所有浏览器似乎都支持这一点

        2020 年更新:大多数主要浏览器(Edge、Firefox、Chrome、Safari、Opera 等)support the feature,已成为the DOM living standard 的一部分。 (截至 2020 年 3 月 5 日)

        不过,我们很快就会看到这一变化,因此您应该可以使用 AbortControllers AbortSignal 取消请求。

        加长版

        如何:

        它的工作方式是这样的:

        第 1 步:创建一个AbortController(目前我只使用了this

        const controller = new AbortController()

        第 2 步:您会收到这样的AbortControllers 信号:

        const signal = controller.signal

        第 3 步:您通过 signal 进行获取,如下所示:

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

        第 4 步:在需要时中止:

        controller.abort();

        这是一个如何工作的示例(适用于 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>

        来源:

        【讨论】:

        • 这个答案是正确的,应该被赞成。但我冒昧地对代码 sn-p 进行了一些编辑,因为它在 Firefox 57+ 中实际上并没有工作——垫片似乎导致它失败(“Err: TypeError: ' RequestInit 的 signal' 成员没有实现接口 AbortSignal。”)并且 slowwly.robertomurray.co.uk 的证书似乎有问题(“此服务器无法证明它是 slowwly.robertomurray.co。英国;它的安全证书来自 *.herokuapp.com。”),所以我将其更改为仅使用 slowwly.robertomurray.co.uk(纯 http)。
        • 但现在它不适用于其他浏览器,例如 Chrome,因为AbortController is not defined。无论如何,这只是一个概念证明,至少 Firefox 57+ 的人可以看到它工作
        • 这是纯 StackOverflow 的黄金,感谢简洁的文章!还有 bugtracker 链接!
        • 现在所有现代浏览器都支持它。 developer.mozilla.org/en-US/docs/Web/API/AbortController/abort见底部表格
        • 谢谢,但我还有一个问题,我们是否应该手动将信号更改回 true 以进行下一次提取??
        【解决方案5】:

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

        适用于 edge 16 (2017-10-17)、firefox 57 (2017-11-14)、桌面 safari 11.1 (2018-03-29)、ios safari 11.4 (2018-03-29)、chrome 67 ( 2018-05-29) 及以后。


        在旧版浏览器上,您可以使用github's whatwg-fetch polyfillAbortController polyfill。你也可以detect older browsers and use the polyfills conditionally

        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
        

        【讨论】:

        • 如果使用 github 的 fetch polyfill,这是可以做到的,只需按照他们自述文件中的说明进行操作:github.com/github/fetch#aborting-requests
        • @FábioSantos 您的评论应该是关于这个问题,还是作为它自己的答案?它看起来并不特定于我的回答。
        • 只是给使用 github fetch polyfill 的人的注意事项。我认为这与您的答案有关,因为 AFAIK 它是最流行的可用 fetch polyfill,并且它填充了您正在使用的函数 fetch。由于旧的浏览器,很多人会使用这个 polyfill。我发现提一下很重要,因为人们只是假设 polyfill 可以解决所有问题,但是这个特定的并没有尝试对 AbortController 进行 polyfill。他们会尝试使用 AbortController,认为它将在旧浏览器中填充,然后繁荣,在极端情况下有一个例外,并且只在旧浏览器上。
        【解决方案6】:

        截至 2018 年 2 月,fetch() 可以在 Chrome 上使用以下代码取消(阅读Using Readable Streams 以启用 Firefox 支持)。 catch() 拾取不会引发错误,这是一个临时解决方案,直到完全采用 AbortController

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

        【讨论】:

        • 这不是 OP 所要求的。他们想取消读取而不是读者。 Fetch 的 promise 直到请求完成后才会解析,这为时已晚取消对服务器的请求。
        【解决方案7】:

        正如@spro 所说,目前还没有合适的解决方案。

        但是,如果您有正在进行的响应并且正在使用 ReadableStream,则可以关闭流以取消请求。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-19
          • 2018-01-22
          • 2011-10-15
          相关资源
          最近更新 更多