AbortController

AbortController 是一个现代浏览器和Node.js提供的Web API,用于中止一个或多个Web请求。它不仅能用于取消fetch请求,还可以中止其他异步任务。下面我们来看看它的核心用法。

🧠 理解 AbortController 的核心

AbortController 本身设计得很简洁,主要包括:

组成部分 说明
abort() 方法 调用它就会触发中止信号。
signal 属性 这是一个AbortSignal对象,用于与DOM请求通信或中止DOM请求。

当你调用 controller.abort() 时:

  • 会在 controller.signal 上触发 abort 事件
  • controller.signal.aborted 属性的值会变为 true

任何关心中止操作的地方,都可以通过监听 signal 上的 abort 事件来响应。

🛠️ 基本用法与常见场景

1. 中止单个 Fetch 请求

这是最常见的用法,具体步骤如下:

  1. 创建控制器const controller = new AbortController();
  2. 传递 Signal:在调用 fetch 时,将 controller.signal 作为选项中的 signal 属性传入。
  3. 中止请求:需要中止时,调用 controller.abort()

当 fetch 请求被中止,其返回的 Promise 会被拒绝,并抛出一个名为 AbortErrorDOMException。因此,务必使用 try...catch 来处理这个错误

// 创建 AbortController 的实例
const controller = new AbortController();

// 假设有按钮触发中止
abortButton.addEventListener('click', () => {
  // 点击按钮时中止请求
  controller.abort();
});

try {
  // 将 signal 传递给 fetch
  const response = await fetch('https://api.example.com/data', {
    signal: controller.signal
  });
  const data = await response.json();
  // 处理数据...
} catch (error) {
  // 判断错误是否为中止操作引起的
  if (error.name === 'AbortError') {
    console.log('请求已被中止');
  } else {
    console.error('其他错误:', error);
  }
}

2. 在 React useEffect 中取消请求

在 React 函数组件中,这是一个非常有用的模式,可以避免组件卸载后尝试更新状态导致的错误

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 1. 在Effect内部创建控制器
    const controller = new AbortController();
    const { signal } = controller;

    // 2. 定义异步函数进行数据获取
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data', { signal });
        const jsonData = await response.json();
        setData(jsonData);
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('获取数据失败:', error);
        }
      }
    };

    fetchData();

    // 3. 清理函数:当依赖变化或组件卸载时,中止请求
    return () => {
      controller.abort();
    };
  }, []); // 依赖数组

  // 渲染...
}

3. 同时中止多个请求

同一个 AbortController 可以用于中止多个请求。只需将它的 signal 传递给每一个 fetch 调用即可。

// 创建一个控制器,可用于中止多个任务
const controller = new AbortController();

// 同时发起多个请求,并传递同一个 signal
const fetchPromises = urls.map(url => 
  fetch(url, { signal: controller.signal })
);

// 当需要时,中止所有使用这个 signal 的请求
cancelButton.addEventListener('click', () => {
  controller.abort();
});

try {
  const responses = await Promise.all(fetchPromises);
  // 处理所有响应...
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('所有请求已被中止');
  } else {
    // 处理其他错误
  }
}

4. 中止非 Fetch 的异步任务

AbortController 是一个通用工具,你也可以用它来中止任何异步任务。方法是监听 signalabort 事件,并在事件触发时执行拒绝操作。

// 创建一个可以中止的 Promise 任务
function doAsyncTask({ signal }) {
  return new Promise((resolve, reject) => {
    // 如果信号已经中止,立即拒绝
    if (signal.aborted) {
      return reject(new DOMException('Aborted', 'AbortError'));
    }

    // 正常的异步操作,例如一个长时间运行的计算或定时器
    const timer = setTimeout(() => {
      resolve('任务完成!');
    }, 5000);

    // 监听中止事件,事件发生时清理并拒绝 Promise
    signal.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Aborted', 'AbortError'));
    });
  });
}

// 使用
const controller = new AbortController();
const { signal } = controller;

// 在需要时中止任务
setTimeout(() => controller.abort(), 2000);

try {
  const result = await doAsyncTask({ signal });
  console.log(result);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('异步任务被中止');
  }
}

5. 使用 AbortSignal 移除事件监听器

addEventListener 的选项参数中可以接收一个 signal。当这个信号被中止时,浏览器会自动移除该事件监听器。这是一种非常方便的管理事件监听的方式。

const controller = new AbortController();
const { signal } = controller;

// 添加事件监听器,并传入 signal
window.addEventListener('resize', () => {
  // 处理 resize 事件
  console.log('窗口大小改变了');
}, { signal }); // 注意这里的选项

// 在不需要时(例如组件卸载),移除所有通过这个 signal 注册的事件监听器
controller.abort();

💡 重要提示

  1. abort() 只能调用一次:一个 AbortController 实例一旦中止,就无法恢复。如果需要再次发起请求,必须创建一个新的 AbortController 实例
  2. 处理 AbortError:捕获错误时,务必通过 error.name === 'AbortError' 来区分请求是被中止还是发生了其他网络错误。对于中止错误,通常不需要做特殊处理,静默处理即可。
  3. this 绑定:注意,在解构 abort 方法时,直接调用解构出来的方法可能会导致 this 指向丢失。安全起见,建议通过 controller.abort() 的方式调用,或者确保正确的 this 绑定。

下面这张流程图直观地展示了 AbortController 在请求中的工作流程和生命周期,特别是它在 React 组件中的典型使用方式:

flowchart TD
    A[组件初始化] --> B[创建 AbortController]
    B --> C[发起 Fetch 请求<br>传入 signal]
    C --> D{请求进行中}
    D --> E{用户主动取消或<br>组件卸载?}
    E -- 是 --> F[调用 controller.abort]
    F --> G[Fetch 请求被中止<br>Promise 被拒绝]
    G --> H[捕获 AbortError<br>可选择静默处理]
    E -- 否 --> I{请求成功?}
    I -- 是 --> J[处理响应数据]
    I -- 否<br>其他错误 --> K[处理其他错误]
    H --> L[生命周期结束]
    J --> L
    K --> L

[图片上传失败...(image-787213-1764668935478)]

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容