AbortSignal

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2019.

备注: 此特性在 Web Worker 中可用。

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

EventTarget AbortSignal

实例属性

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

AbortSignal.aborted 只读

一个 Boolean,表示与之通信的请求是否被中止(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() 创建一个中止控制器,然后使用 AbortController.signal 属性获取与它关联的 AbortSignal 对象的引用。

fetch 请求开始时,我们将 AbortSignal 作为一个选项传递进请求的 option 对象中(见下面的 {signal})。这个将信号和控制器与 fetch 请求相关联,并且允许我们通过调用 AbortController.abort() 中止它。你可以看见当中止按钮(abortBtn)被点击时,第二个事件监听器触发,使 fetch 操作被中止。

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("下载已中止");
  }
});

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

  try {
    const response = await fetch(url, { signal });
    console.log("下载完毕", response);
    // 进一步处理请求
  } catch (err) {
    console.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();
  // 下一行代码将抛出 `AbortError`
  const text = await response.text();
  console.log(text);
}

你可以在 GitHub 上找到一个完整、可运行的示例;你也可以参见在线演示

中止超时的读取操作

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

以下代码片段展示了如何成功地下载一个文件或者在五秒钟后处理一个超时的错误。注意,当出现超时时,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("超时:获取结果的事件超过了 5 秒!");
  } else if (err.name === "AbortError") {
    console.error("Fetch 操作被用户中止(如浏览器停止按钮、关闭标签等)");
  } else {
    // 网络错误,或其他问题
    console.error(`错误:类型:${err.name},消息:${err.message}`);
  }
}

超时或显式中止 fetch

如果要从多个信号中中止,可以使用 AbortSignal.any() 将它们合并为一个信号。下面的示例使用 fetch 进行了演示:

js
try {
  const controller = new AbortController();
  const timeoutSignal = AbortSignal.timeout(5000);
  const res = await fetch(url, {
    // 任意一个信号中止时,整个操作会被中止
    signal: AbortSignal.any([controller.signal, timeoutSignal]),
  });
  const body = await res.json();
} catch (e) {
  if (e.name === "AbortError") {
    // 通知用户操作中止
  } else if (e.name === "TimeoutError") {
    // 通知用户超时
  } else {
    // 网络错误,或其他问题
    console.log(`类型:${e.name},消息:${e.message}`);
  }
}

备注: 与使用 AbortSignal.timeout() 不同,没有方法来判断最终中止是否由超时引起。

实现可中止的 API

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

基于 Promise 的 API 应对中止信号做出响应,拒绝任何未确定的承诺,并使用 AbortSignal 中止 reason。例如,请看下面的 myCoolPromiseAPI,它接收一个信号并返回一个 promise。如果信号已被中止,或检测到中止事件,则会立即拒绝该 promise。否则,它将正常完成,然后兑现 promise。

js
function myCoolPromiseAPI(/* …, */ { signal }) {
  return new Promise((resolve, reject) => {
    // 如果信号已经被中止,立即抛出错误,以拒绝 promise
    if (signal.aborted) {
      reject(signal.reason);
    }

    // 执行 API 主要的目标
    // 当完成时调用 resolve(result)

    // 监听 'abort' 信号
    signal.addEventListener("abort", () => {
      // 停止主要任务
      // 以中止理由拒绝 promise
      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 接口可能会以类似的方式做出反应。在某些情况下,接收信号可能是有意义的。

规范

Specification
DOM Standard
# interface-AbortSignal

浏览器兼容性

BCD tables only load in the browser

参见