AbortSignal

Baseline 广泛可用 *

此功能已成熟,可跨多种设备和浏览器版本工作。它自 ⁨2018 年 4 月⁩ 起已在所有浏览器中可用。

* 此特性的某些部分可能存在不同级别的支持。

注意:此功能在 Web Workers 中可用。

AbortSignal 接口表示一个信号对象,允许您与异步操作(如 fetch 请求)进行通信,并通过 AbortController 对象在需要时中止该操作。

EventTarget AbortSignal

实例属性

还继承了其父接口 EventTarget 的属性。

AbortSignal.aborted 只读

一个 布尔值,指示信号正在通信的请求是否已被中止(true)或未被中止(false)。

AbortSignal.reason 只读

信号中止后,提供中止原因的 JavaScript 值。

静态方法

还继承了其父接口 EventTarget 的方法。

AbortSignal.abort()

返回一个已设置为已中止的 AbortSignal 实例。

AbortSignal.any()

返回一个 AbortSignal,当给定的任何中止信号中止时,它将中止。

AbortSignal.timeout()

返回一个 AbortSignal 实例,该实例将在指定时间后自动中止。

实例方法

还继承了其父接口 EventTarget 的方法。

AbortSignal.throwIfAborted()

如果信号已被中止,则抛出信号的中止 reason;否则不执行任何操作。

事件

还继承了其父接口 EventTarget 的事件。

使用 addEventListener() 侦听此事件,或将事件监听器分配给此接口的 oneventname 属性。

abort

当信号正在通信的异步操作被中止时调用。也可通过 onabort 属性访问。

示例

使用显式信号中止 fetch 操作

以下代码片段展示了如何使用信号通过 Fetch API 中止视频下载。

我们首先定义一个 AbortController 变量。

在每个 fetch 请求之前,我们使用 AbortController() 构造函数创建一个新的控制器,然后通过 AbortController.signal 属性获取对其关联 AbortSignal 对象的引用。

注意: 一个 AbortSignal 只能使用一次。一旦它被中止,任何使用相同信号的 fetch 调用都将立即被拒绝。

fetch 请求被发起时,我们将 AbortSignal 作为请求选项对象中的一个选项(下面的 { signal })传入。这会将信号和控制器与 fetch 请求关联起来,并通过调用 AbortController.abort() 来中止它,正如下面的第二个事件监听器中所见。

当调用 abort() 时,fetch() Promise 会以一个名为 AbortErrorDOMException 被拒绝。

js
let controller;
const url = "video.mp4";

const downloadBtn = document.querySelector(".download");
const abortBtn = document.querySelector(".abort");

downloadBtn.addEventListener("click", fetchVideo);

abortBtn.addEventListener("click", () => {
  if (controller) {
    controller.abort();
    console.log("Download aborted");
  }
});

async function fetchVideo() {
  controller = new AbortController();
  const signal = controller.signal;

  try {
    const response = await fetch(url, { signal });
    console.log("Download complete", response);
    // process response further
  } catch (err) {
    console.error(`Download error: ${err.message}`);
  }
}

如果请求在 fetch() 调用已完成但响应体尚未读取之前被中止,那么尝试读取响应体会因 AbortError 异常而被拒绝。

js
async function get() {
  const controller = new AbortController();
  const request = new Request("https://example.org/get", {
    signal: controller.signal,
  });

  const response = await fetch(request);
  controller.abort();
  // The next line will throw `AbortError`
  const text = await response.text();
  console.log(text);
}

您可以在 GitHub 上找到完整的可运行示例;您也可以 在线查看

使用超时中止 fetch 操作

如果您需要在超时时中止操作,可以使用静态的 AbortSignal.timeout() 方法。它返回一个 AbortSignal,该信号将在指定毫秒数后自动超时。

以下代码片段展示了如何成功下载文件,或在 5 秒后处理超时错误。请注意,当发生超时时,fetch() Promise 会以一个 TimeoutError DOMException 被拒绝。这使得代码可以区分超时(可能需要用户通知)和用户中止。

js
const url = "video.mp4";

try {
  const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
  const result = await res.blob();
  // …
} catch (err) {
  if (err.name === "TimeoutError") {
    console.error("Timeout: It took more than 5 seconds to get the result!");
  } else if (err.name === "AbortError") {
    console.error(
      "Fetch aborted by user action (browser stop button, closing tab, etc.",
    );
  } else {
    // A network error, or some other problem.
    console.error(`Error: type: ${err.name}, message: ${err.message}`);
  }
}

使用超时或显式中止来中止 fetch

如果您想从多个信号中中止,可以使用 AbortSignal.any() 将它们合并成一个信号。以下示例使用 fetch 展示了这一点。

js
try {
  const controller = new AbortController();
  const timeoutSignal = AbortSignal.timeout(5000);
  const res = await fetch(url, {
    // This will abort the fetch when either signal is aborted
    signal: AbortSignal.any([controller.signal, timeoutSignal]),
  });
  const body = await res.json();
} catch (e) {
  if (e.name === "AbortError") {
    // Notify the user of abort.
  } else if (e.name === "TimeoutError") {
    // Notify the user of timeout
  } else {
    // A network error, or some other problem.
    console.log(`Type: ${e.name}, Message: ${e.message}`);
  }
}

注意: 与使用 AbortSignal.timeout() 不同,无法确定最终的中止是由超时引起的。

实现可中止的 API

需要支持中止的 API 可以接受一个 AbortSignal 对象,并利用其状态在需要时触发中止信号处理。

基于 Promise 的 API 应该通过用 AbortSignal 的中止 reason 来拒绝任何尚未解决的 Promise 来响应中止信号。例如,考虑以下 myCoolPromiseAPI,它接受一个信号并返回一个 Promise。如果信号已被中止,或者检测到中止事件,Promise 会立即被拒绝。否则,它会正常完成,然后解析 Promise。

js
function myCoolPromiseAPI(/* …, */ { signal }) {
  return new Promise((resolve, reject) => {
    // If the signal is already aborted, immediately throw in order to reject the promise.
    if (signal.aborted) {
      reject(signal.reason);
      return;
    }

    // Perform the main purpose of the API
    // Call resolve(result) when done.

    // Watch for 'abort' signals
    signal.addEventListener("abort", () => {
      // Stop the main operation
      // Reject the promise with the abort reason.
      reject(signal.reason);
    });
  });
}

API 随后可以像这样使用。请注意,AbortController.abort() 被调用来中止操作。

js
const controller = new AbortController();
const signal = controller.signal;

startSpinner();

myCoolPromiseAPI({ /* …, */ signal })
  .then((result) => {})
  .catch((err) => {
    if (err.name === "AbortError") return;
    showUserErrorMessage();
  })
  .then(() => stopSpinner());

controller.abort();

不返回 Promise 的 API 可能会以类似的方式做出反应。在某些情况下,吸收信号可能是有意义的。

规范

规范
DOM
# interface-AbortSignal

浏览器兼容性

另见