如何取消HTTP fetch()请求?


Answers:


280

TL / DR:

fetch现在支持signal截至2017年9月20日的参数,但目前并非所有浏览器都支持此参数

2020更新:大多数主流浏览器(Edge,Firefox,Chrome,Safari,Opera和其他一些浏览器)都支持该功能,该功能已成为DOM生活标准的一部分。(截至2020年3月5日)

这是我们很快就会看到的更改,因此您应该可以使用AbortControllers 取消请求AbortSignal

长版

如何:

它的工作方式是这样的:

第1步:您创建了一个AbortController(现在我只使用了这个

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>

资料来源:


2
这个答案是正确的,应该予以赞成。但是我还是自由地对代码段进行了一些编辑,因为按原样它实际上在Firefox 57+中不起作用-填充程序似乎导致其失败(“ Err:TypeError:RequestInit的'signal'成员没有实现接口AbortSignal。”,并且slowwly.robertomurray.co.uk的证书似乎存在一些问题(“此服务器无法证明它是slowwly.robertomurray.co.uk;其安全证书来自* .herokuapp.com。”),因此我将其更改为仅使用slowwly.robertomurray.co.uk(普通http)。
sideshowbarker

3
但是现在,它不适用于其他浏览器(例如Chrome),因为AbortController is not defined。无论如何,这只是概念的证明,至少使用Firefox 57+的人可以看到它的正常运行
SudoPlz

3
这是纯StackOverflow黄金版,感谢您的简要撰写!以及bugtracker链接!
Kjellski

3
现在,所有现代浏览器都支持它。developer.mozilla.org/en-US/docs/Web/API/AbortController/abort看到底部的表
亚历Ivasyuv

2
谢谢,但是我还有一个问题,我们是否应该为下一次手动获取将信号改回true?
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();

适用于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的whatwg-fetch polyfillAbortController polyfill。您也可以检测到较旧的浏览器并有条件地使用polyfills

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的获取填充工具,这是可以做到的它,只要按照他们的自述文件的说明:github.com/github/fetch#aborting-requests
法比奥·桑托斯

@FábioSantos您的评论应该是关于问题的答案,还是本身的答案?它看起来并不特定于我的答案。
Jayen

只是对使用github提取polyfill的人的说明。我认为这与您的答案有关,因为AFAIK是最流行的fetch polyfill,它可以填充您正在使用的功能fetch。由于旧的浏览器,很多人将使用此polyfill。我发现提到这一点很重要,因为人们只是假设polyfill可以修复所有问题,但是这一特定的人不会尝试polyfill AbortController。他们会尝试使用AbortController,以为它将在旧的浏览器中被填充,并且繁荣发展,在极端情况下,只有在旧的浏览器中才有例外。
法比奥·桑托斯

5

自2018年2月起,fetch()可以在Chrome上使用以下代码取消(阅读使用可读流以启用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();
        })
      }
    }
  }
}

2
这不是OP所要求的。他们想取消读取而不是读取器。直到请求完成后,获取的承诺才会解决,为时已晚,无法取消对服务器的请求。
拉利

3

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

0

让我们来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;
}

请记住,该代码未经测试!让我知道您是否已经测试过,但没有成功。它可能会警告您尝试覆盖JavaScript官方库中的'fetch'函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.