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


当前回答

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

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

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 polyfill和AbortController 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

让我们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'函数,它可能会给你警告。

TL / diana:

fetch现在支持信号参数截至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

第三步:像这样传递获取的信号:

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

第四步:需要的时候就放弃:

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>

来源:

AbortController的最终版本已经添加到DOM规范中 取回规范对应的PR现在被合并了。 跟踪AbortController实现的浏览器错误可以在这里找到:Firefox: #1378342, Chromium: #750599, WebKit: #174980, Edge: #13009916。