你真的会用 Fetch 吗?深入 AbortController 实现请求取消

4 min

1. Fetch API:现代 Web 请求的基石

Fetch API 已经取代了老旧的 XMLHttpRequest,成为浏览器中发起 HTTP 请求的标准方式。它基于 Promise,提供了更简洁、更强大的请求能力。

然而,Fetch API 默认并没有提供一个直接取消请求的机制。在许多场景下,请求取消是一个非常重要的功能:

  • 性能优化: 用户快速切换页面或输入搜索词时,取消旧的、不再需要的请求可以节省带宽和服务器资源。
  • 避免竞态条件: 在“搜索即时输入”的场景中,如果旧的请求比新的请求返回得慢,可能会导致 UI 显示过时的数据。
  • 防止内存泄漏: 在组件生命周期中发起的请求,如果组件在请求完成前被卸载,尝试更新一个不存在的组件可能会导致错误或内存泄漏。

2. AbortController:通用的取消信号

AbortController 是一个通用的 Web API,它提供了一种机制来中止一个或多个 Web 请求。它独立于 Fetch API,但可以与 Fetch 完美配合。

AbortController 的核心思想是:

  1. 创建一个 AbortController 实例。
  2. 从这个实例中获取一个 AbortSignal 对象。
  3. AbortSignal 传递给可中止的 Web API(如 fetch)。
  4. 当你需要取消时,调用 AbortController 实例的 abort() 方法。

3. AbortControllerFetch 的结合

让我们通过一个例子来看看如何使用 AbortController 来取消 Fetch 请求。

const controller = new AbortController();
const signal = controller.signal; // 获取信号对象

async function fetchDataWithCancellation(url) {
  try {
    console.log('Fetching data...');
    const response = await fetch(url, { signal }); // 将信号传递给 fetch
    const data = await response.json();
    console.log('Data received:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch request was aborted.');
    } else {
      console.error('Fetch error:', error);
    }
  }
}

// 示例用法
const promise = fetchDataWithCancellation('https://jsonplaceholder.typicode.com/posts/1');

// 假设在 500 毫秒后,我们决定取消这个请求
setTimeout(() => {
  controller.abort(); // 调用 abort() 方法取消请求
  console.log('Request aborted by timeout.');
}, 500);

controller.abort() 被调用时,fetch 请求会立即中止,并抛出一个 AbortError。你需要在 catch 块中捕获这个错误,并检查 error.name === 'AbortError' 来区分是取消操作还是其他网络错误。

4. 实际应用场景

a. 搜索即时输入 (Search-as-you-type)

当用户在搜索框中快速输入时,每次输入都可能触发一个新的请求。我们可以取消前一个未完成的请求,只保留最新的请求。

let currentController = null;

document.getElementById('searchInput').addEventListener('input', (event) => {
  if (currentController) {
    currentController.abort(); // 取消上一个请求
  }
  currentController = new AbortController();
  const signal = currentController.signal;

  const query = event.target.value;
  if (query.length > 2) {
    fetchDataWithCancellation(`/api/search?q=${query}`, signal);
  }
});

b. 组件卸载时取消请求 (Component Unmount)

在 React 或 Vue 等框架中,当组件卸载时,取消其内部发起的未完成请求可以有效避免内存泄漏和不必要的 UI 更新。

// React 示例
useEffect(() => {
  const controller = new AbortController();
  fetchDataWithCancellation('/api/data', controller.signal);

  return () => {
    controller.abort(); // 组件卸载时取消请求
  };
}, []);

结论

AbortController 是现代 Web 开发中一个不可或缺的工具。它为 Fetch API 和其他异步操作提供了原生的取消机制,帮助开发者构建更健壮、更高效、用户体验更好的应用。掌握 AbortController 的使用,是成为一名优秀前端开发者的必备技能之一。