2022 年 4 月 13 日

获取:中止

我们知道,fetch 返回一个 Promise。而 JavaScript 通常没有“中止” Promise 的概念。那么我们如何取消正在进行的 fetch 呢?例如,如果用户在我们网站上的操作表明不再需要 fetch 了。

有一个专门用于此目的的内置对象:AbortController。它不仅可以用于中止 fetch,还可以用于中止其他异步任务。

使用方法非常简单

AbortController 对象

创建控制器

let controller = new AbortController();

控制器是一个非常简单的对象。

  • 它只有一个方法 abort()
  • 以及一个属性 signal,允许在其上设置事件监听器。

当调用 abort()

  • controller.signal 会发出 "abort" 事件。
  • controller.signal.aborted 属性变为 true

通常,我们在过程中有两个参与者

  1. 执行可取消操作的一方,它在 controller.signal 上设置一个监听器。
  2. 取消操作的一方:它在需要时调用 controller.abort()

以下是一个完整的示例(还没有使用 fetch

let controller = new AbortController();
let signal = controller.signal;

// The party that performs a cancelable operation
// gets the "signal" object
// and sets the listener to trigger when controller.abort() is called
signal.addEventListener('abort', () => alert("abort!"));

// The other party, that cancels (at any point later):
controller.abort(); // abort!

// The event triggers and signal.aborted becomes true
alert(signal.aborted); // true

正如我们所见,AbortController 只是在调用 abort() 时传递 abort 事件的一种方式。

我们可以自己实现相同类型的事件监听,而无需 AbortController 对象。

但有价值的是,fetch 知道如何使用 AbortController 对象。它已集成到其中。

与 fetch 一起使用

为了能够取消 fetch,将 AbortControllersignal 属性作为 fetch 选项传递

let controller = new AbortController();
fetch(url, {
  signal: controller.signal
});

fetch 方法知道如何使用 AbortController。它将监听 signal 上的 abort 事件。

现在,要中止,请调用 controller.abort()

controller.abort();

我们完成了:fetchsignal 获取事件并中止请求。

当 fetch 被中止时,它的 promise 会拒绝并抛出一个 AbortError 错误,因此我们应该处理它,例如在 try..catch 中。

以下是一个完整的示例,其中 fetch 在 1 秒后被中止

// abort in 1 second
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

try {
  let response = await fetch('/article/fetch-abort/demo/hang', {
    signal: controller.signal
  });
} catch(err) {
  if (err.name == 'AbortError') { // handle abort()
    alert("Aborted!");
  } else {
    throw err;
  }
}

AbortController 可扩展

AbortController 可扩展。它允许同时取消多个 fetch。

以下是一个代码草图,它并行获取多个 urls,并使用单个控制器中止所有这些请求

let urls = [...]; // a list of urls to fetch in parallel

let controller = new AbortController();

// an array of fetch promises
let fetchJobs = urls.map(url => fetch(url, {
  signal: controller.signal
}));

let results = await Promise.all(fetchJobs);

// if controller.abort() is called from anywhere,
// it aborts all fetches

如果我们有自己的异步任务,与 fetch 不同,我们可以使用单个 AbortController 来停止这些任务,以及 fetch。

我们只需要在我们的任务中监听它的 abort 事件

let urls = [...];
let controller = new AbortController();

let ourJob = new Promise((resolve, reject) => { // our task
  ...
  controller.signal.addEventListener('abort', reject);
});

let fetchJobs = urls.map(url => fetch(url, { // fetches
  signal: controller.signal
}));

// Wait for fetches and our task in parallel
let results = await Promise.all([...fetchJobs, ourJob]);

// if controller.abort() is called from anywhere,
// it aborts all fetches and ourJob

总结

  • AbortController 是一个简单的对象,当调用 abort() 方法时,它会在其 signal 属性上生成一个 abort 事件(并将 signal.aborted 设置为 true)。
  • fetch 与它集成:我们将 signal 属性作为选项传递,然后 fetch 监听它,因此可以中止 fetch
  • 我们可以在代码中使用 AbortController。 "调用 abort()" → “监听 abort 事件” 的交互简单且通用。即使没有 fetch,我们也可以使用它。
教程地图

评论

在评论之前阅读...
  • 如果您有改进建议,请提交 GitHub 问题或拉取请求,而不是评论。
  • 如果您不理解文章中的某些内容,请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行代码,请将它们包装在 <pre> 标签中,对于超过 10 行的代码,请使用沙箱 (plnkrjsbincodepen…)