我第一次接触 AbortController 是在一个深夜调试项目的时候。当时我向 HolySheep AI 发送了一个很长的请求,结果用户不小心点了返回键,页面跳转了,但请求还在后台跑,不仅浪费了用户流量,还导致页面逻辑混乱。从那以后,我就养成了给所有 API 请求加上取消机制的习惯。今天我就把这套经验分享给你,保证你看完就能上手。

什么是 AbortController?为什么要取消请求?

你可以把 AbortController 想象成一个「遥控器」。当你按电视遥控器的关闭键,电视就会停止播放。AbortController 就是你给浏览器或 Node.js 发送的「关闭指令」,让它停止正在运行的请求。

在实际开发中,取消请求的场景非常常见:

根据 HolySheep AI 的实测数据,国内直连延迟可以控制在 50ms 以内,但如果是复杂的推理任务(如 GPT-4.1),单个请求可能需要几十秒。合理使用取消机制,可以为用户节省约 30% 的等待时间和流量费用。

AbortController 基本用法:三行代码搞定

别被这个名字吓到,其实它用起来超级简单。核心就三步:创建控制器、关联请求、触发取消。

第一步:创建 AbortController 实例

// 创建一个「遥控器」
const controller = new AbortController();

第二步:把遥控器和请求绑定

// 把信号线连接到请求上
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
  },
  body: JSON.stringify({
    model: 'gpt-4.1',
    messages: [{ role: 'user', content: '你好' }]
  }),
  signal: controller.signal  // 关键!这行代码把遥控器和请求连起来了
});

第三步:调用 cancel() 方法停止请求

// 按下遥控器的关闭键
controller.abort();

就这么简单!当你调用 controller.abort() 时,fetch 请求会立即被取消,浏览器会抛出一个 AbortError 错误,你可以通过 try-catch 来捕获它。

实战案例:取消 HolySheep AI 对话请求

现在让我们来一个完整的实战案例。假设你要做一个聊天应用,用户可以随时取消正在生成的回复。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>AI 聊天演示</title>
</head>
<body>
  <h1>HolySheep AI 聊天演示</h1>
  <button id="startBtn">开始对话</button>
  <button id="cancelBtn" disabled>取消请求</button>
  <div id="output"></div>

  <script>
    let currentController = null;

    async function sendChatRequest() {
      // 禁用开始按钮,启用取消按钮
      document.getElementById('startBtn').disabled = true;
      document.getElementById('cancelBtn').disabled = false;
      document.getElementById('output').innerHTML = '正在等待回复...';

      // 每次发送请求都创建新的 controller
      currentController = new AbortController();
      
      try {
        const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
          },
          body: JSON.stringify({
            model: 'gpt-4.1',
            messages: [
              { role: 'system', content: '你是一个有帮助的助手' },
              { role: 'user', content: '请用100字介绍一下人工智能的发展历史' }
            ],
            max_tokens: 500
          }),
          signal: currentController.signal
        });

        const data = await response.json();
        
        if (data.choices && data.choices[0]) {
          document.getElementById('output').innerHTML = 
            '<p>AI 回复:</p><p>' + data.choices[0].message.content + '</p>';
        }
      } catch (error) {
        if (error.name === 'AbortError') {
          document.getElementById('output').innerHTML = '⏹️ 请求已被用户取消';
        } else {
          document.getElementById('output').innerHTML = '❌ 发生错误:' + error.message;
        }
      } finally {
        document.getElementById('startBtn').disabled = false;
        document.getElementById('cancelBtn').disabled = true;
        currentController = null;
      }
    }

    function cancelRequest() {
      if (currentController) {
        currentController.abort();
      }
    }

    document.getElementById('startBtn').addEventListener('click', sendChatRequest);
    document.getElementById('cancelBtn').addEventListener('click', cancelRequest);
  </script>
</body>
</html>

在这个例子中,我每次点击「开始对话」都会创建一个新的 AbortController 实例。这样做的好处是:每次请求都是独立的,取消操作不会影响到其他请求。HolySheep AI 支持微信/支付宝充值,汇率仅需 ¥7.3 = $1(官方汇率),注册即送免费额度,非常适合新手练手。

高级用法:超时自动取消

有时候用户没有手动取消,但请求本身运行太久了,这时候我们需要设置「超时」来自动取消请求。

/**
 * 带超时功能的请求封装
 * @param {string} url - API 地址
 * @param {object} options - fetch 选项
 * @param {number} timeoutMs - 超时毫秒数,默认 30 秒
 */
async function fetchWithTimeout(url, options = {}, timeoutMs = 30000) {
  const controller = new AbortController();
  
  // 设置定时器,30 秒后自动取消
  const timeoutId = setTimeout(() => {
    controller.abort();
    console.log('⏰ 请求超时,已自动取消');
  }, timeoutMs);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    
    // 清除定时器,因为请求已经完成了
    clearTimeout(timeoutId);
    
    return response;
  } catch (error) {
    clearTimeout(timeoutId); // 也要清除,防止内存泄漏
    
    if (error.name === 'AbortError') {
      if (error.message === 'Aborted') {
        throw new Error('请求超时(超过 ' + (timeoutMs / 1000) + ' 秒)');
      }
    }
    throw error;
  }
}

// 使用示例:向 HolySheep AI 发送请求,10 秒超时
async function chatWithTimeout() {
  try {
    const response = await fetchWithTimeout(
      'https://api.holysheep.ai/v1/chat/completions',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
        },
        body: JSON.stringify({
          model: 'gemini-2.5-flash', // 这个模型速度快,适合需要超时的场景
          messages: [{ role: 'user', content: '你好' }]
        })
      },
      10000 // 10 秒超时
    );
    
    const data = await response.json();
    console.log('回复:', data.choices[0].message.content);
  } catch (error) {
    console.error('出错了:', error.message);
  }
}

chatWithTimeout();

我自己在项目中使用这个封装函数时,通常会把超时时间设为 30 秒到 60 秒之间。对于简单的对话请求,30 秒足够了;如果是需要复杂推理的请求(比如使用 Claude Sonnet 4.5,它的价格是 $15/MTok),可以考虑延长到 60 秒。

AbortController 完整 API 详解

AbortController 其实只有两个属性和方法:

AbortSignal 还有一个很实用的功能:你可以用它来监听取消事件。

const controller = new AbortController();

// 监听取消事件(虽然实际场景中很少用)
controller.signal.addEventListener('abort', () => {
  console.log('请求被取消了!');
  // 在这里可以做清理工作,比如关闭 loading 动画等
});

// 或者用 onabort 属性
controller.signal.onabort = () => {
  console.log('另一种写法,同样可以监听取消事件');
};

// 触发取消
controller.abort();

// 输出:请求被取消了!/ 另一种写法,同样可以监听取消事件

常见报错排查

错误一:请求已经被取消,但代码没有捕获错误

// ❌ 错误写法:没有 try-catch 包裹
const response = await fetch(url, { signal: controller.signal });
const data = await response.json();

// ✅ 正确写法:加上错误处理
try {
  const response = await fetch(url, { signal: controller.signal });
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('请求被取消了');
  } else {
    console.error('其他错误:', error);
  }
}

报错信息Uncaught (in promise) DOMException: The user aborted a request.

解决方法:必须在 fetch 调用外层包裹 try-catch,或者使用 .catch() 来处理取消错误。

错误二:重复调用 abort() 导致的问题

// ❌ 错误写法:多次调用 abort
const controller = new AbortController();
fetch(url, { signal: controller.signal }); // 请求 1
controller.abort(); // 取消请求 1
fetch(url, { signal: controller.signal }); // 请求 2 - 这里可能会出问题!

// ✅ 正确写法:一个请求对应一个 controller
const controller1 = new AbortController();
fetch(url, { signal: controller1.signal }); // 请求 1
controller1.abort(); // 取消请求 1

const controller2 = new AbortController();
fetch(url, { signal: controller2.signal }); // 请求 2 - 独立的 controller

报错信息InvalidStateError: The request has already been aborted.

解决方法:每次发起新请求时,都要创建新的 AbortController 实例,不要复用旧的。

错误三:使用 AbortController.with 指定超时时间时忘记兼容处理

// ❌ 错误写法:直接使用 AbortSignal.timeout(旧浏览器不兼容)
const response = await fetch(url, {
  signal: AbortSignal.timeout(5000) // 一些旧版浏览器不支持这个 API!
});

// ✅ 正确写法:自己封装超时函数
function createTimeoutSignal(ms) {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), ms);
  return controller.signal;
}

const response = await fetch(url, {
  signal: createTimeoutSignal(5000)
});

报错信息TypeError: AbortSignal.timeout is not a function

解决方法:如果你需要兼容旧版浏览器,建议使用上面自己封装的超时函数,而不是依赖 AbortSignal.timeout() 这个较新的 API。

错误四:取消请求后没有清理定时器导致内存泄漏

// ❌ 错误写法:没有清理定时器
function fetchWithTimeout(url, options, timeoutMs) {
  const controller = new AbortController();
  
  setTimeout(() => {
    controller.abort();
  }, timeoutMs);
  // 如果请求 3 秒就完成了,定时器还在内存里!

  return fetch(url, { signal: controller.signal });
}

// ✅ 正确写法:使用 finally 确保清理
function fetchWithTimeout(url, options, timeoutMs) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  return fetch(url, { ...options, signal: controller.signal })
    .finally(() => clearTimeout(timeoutId)); // 请求结束后清除定时器
}

报错信息:长时间运行后浏览器变卡,可能出现内存泄漏警告。

解决方法:使用 .finally() 或在 try-catchfinally 块中清理定时器。

在 React 和 Vue 中使用 AbortController

很多前端开发者是在 React 或 Vue 项目中使用 API 请求的,这里我补充一下这两个框架的用法。

// React Hook 版本
import { useState, useEffect } from 'react';

function ChatComponent() {
  const [messages, setMessages] = useState([]);
  const [loading, setLoading] = useState(false);
  const [controller, setController] = useState(null);

  const sendMessage = async (content) => {
    // 创建新的 controller
    const newController = new AbortController();
    setController(newController);
    setLoading(true);

    try {
      const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
        },
        body: JSON.stringify({
          model: 'deepseek-v3.2', // 价格便宜,只需 $0.42/MTok
          messages: [...messages, { role: 'user', content }]
        }),
        signal: newController.signal
      });

      const data = await response.json();
      setMessages(prev => [...prev, data.choices[0].message]);
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('请求已取消');
      }
    } finally {
      setLoading(false);
    }
  };

  const cancelRequest = () => {
    if (controller) {
      controller.abort();
    }
  };

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

  return (
    <div>
      <button onClick={() => sendMessage('你好')} disabled={loading}>
        发送
      </button>
      <button onClick={cancelRequest} disabled={!loading}>
        取消
      </button>
    </div>
  );
}

我强烈建议在组件卸载的 useEffect cleanup 函数中也调用 abort(),这样可以防止组件卸载后状态更新导致的「Cannot update unmounted component」警告。

总结

AbortController 是现代 JavaScript 中处理异步请求取消的标准方案,它的核心优势是:

在 AI API 调用场景中,合理使用 AbortController 尤为重要。以 HolySheep AI 为例,它提供了国内直连 <50ms 的超低延迟,但复杂模型(如 GPT-4.1,$8/MTok)的推理时间仍然较长。有了取消机制,用户可以随时停止不满意的请求,避免浪费额度。

建议你在所有向外发起的 API 请求中,都加入 AbortController 支持,尤其是:搜索功能、实时聊天、文件上传等用户可能频繁操作的场景。养成这个好习惯,能让你的应用体验提升一个档次。

👉 免费注册 HolySheep AI,获取首月赠额度