264

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

4

7 回答 7

383

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 步:你通过signalfetch 像这样:

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>

资料来源:

于 2017-11-12T15:39:27.740 回答
30

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

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
于 2017-10-17T08:03:17.693 回答
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();
        })
      }
    }
  }
}
于 2018-02-22T16:53:36.017 回答
3

让我们 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' 函数。

于 2020-05-08T13:33:12.280 回答
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();
});
于 2017-09-12T18:16:59.587 回答
0

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

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
于 2021-04-22T11:17:19.417 回答
-1

这适用于浏览器和 nodejs实时浏览器演示

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 
于 2020-09-27T13:53:03.900 回答