浅析什么是AbortController API、基础用法、常见使用场景、使用最佳实践、注意事项及陷阱
一、什么是 AbortController?
通俗理解: 就像给异步操作装了一个"紧急停止按钮"。想象你在网上点外卖:
- 下单后发现地址填错了,想取消 → 这就是 abort
- 如果外卖已经送出,取消就失败了 → 请求已完成,abort 无效
二、基础用法
1、最简单的例子
// 创建控制器
const controller = new AbortController()
// 发起请求,传入 signal
fetch('/api/data', {
signal: controller.signal
})
// 3 秒后取消请求
setTimeout(() => {
controller.abort()
}, 3000)
2、核心概念
const controller = new AbortController()
// ① signal: 信号对象(传给请求)
const signal = controller.signal
// ② abort(): 取消方法(主动调用)
controller.abort()
// ③ aborted: 是否已取消(状态查询)
console.log(signal.aborted) // true/false
// ④ 监听取消事件
signal.addEventListener('abort', () => {
console.log('请求被取消了')
})
三、常见使用场景
场景 1:搜索防抖(最常用)
let controller: AbortController | null = null
const handleSearch = async (keyword: string) => {
// 取消上一次搜索
controller?.abort()
// 创建新的控制器
controller = new AbortController()
try {
const response = await fetch(`/api/search?q=${keyword}`, {
signal: controller.signal
})
const data = await response.json()
displayResults(data)
} catch (error) {
// 忽略取消错误
if (error.name === 'AbortError') {
console.log('搜索已取消')
return
}
// 处理真正的错误
console.error('搜索失败:', error)
}
}
// 用户快速输入:a → ab → abc
// 只有最后一次 "abc" 的请求会生效
场景 2:超时控制
const fetchWithTimeout = async (url: string, timeout: number = 5000) => {
const controller = new AbortController()
// 设置超时
const timeoutId = setTimeout(() => {
controller.abort()
}, timeout)
try {
const response = await fetch(url, {
signal: controller.signal
})
clearTimeout(timeoutId) // 成功后清除定时器
return await response.json()
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('请求超时')
}
throw error
}
}
// 使用
try {
const data = await fetchWithTimeout('/api/slow', 3000)
} catch (error) {
console.error(error.message) // "请求超时"
}
场景 3:组件卸载时取消请求
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'
const data = ref([])
let controller: AbortController | null = null
const loadData = async () => {
controller = new AbortController()
try {
const response = await fetch('/api/data', {
signal: controller.signal
})
data.value = await response.json()
} catch (error) {
if (error.name !== 'AbortError') {
console.error(error)
}
}
}
// 组件卸载时取消请求
onBeforeUnmount(() => {
controller?.abort()
})
loadData()
</script>
场景 4:多个请求共享一个 controller
const controller = new AbortController()
// 同时发起多个请求
Promise.all([
fetch('/api/user', { signal: controller.signal }),
fetch('/api/posts', { signal: controller.signal }),
fetch('/api/comments', { signal: controller.signal })
])
// 一次性取消所有请求
controller.abort()
场景 5:手动取消按钮
<template>
<div>
<button @click="loadData" :disabled="loading">加载数据</button>
<button @click="cancelRequest" :disabled="!loading">取消</button>
<p v-if="loading">加载中...</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const loading = ref(false)
let controller: AbortController | null = null
const loadData = async () => {
controller = new AbortController()
loading.value = true
try {
const response = await fetch('/api/large-data', {
signal: controller.signal
})
const data = await response.json()
console.log(data)
} catch (error) {
if (error.name === 'AbortError') {
console.log('用户取消了加载')
}
} finally {
loading.value = false
}
}
const cancelRequest = () => {
controller?.abort()
}
</script>
四、最佳实践
✅ 1. 总是检查 AbortError
try {
await fetch(url, { signal })
} catch (error) {
if (error.name === 'AbortError') {
// 这是正常的取消,不是错误
console.log('请求已取消')
return
}
// 处理真正的错误
handleError(error)
}
✅ 2. 封装可复用的 Hook(Vue)
// composables/useAbortableFetch.ts
import { ref, onBeforeUnmount } from 'vue'
export function useAbortableFetch<T>() {
const loading = ref(false)
const error = ref<Error | null>(null)
const data = ref<T | null>(null)
let controller: AbortController | null = null
const execute = async (url: string, options = {}) => {
// 取消上一次请求
controller?.abort()
controller = new AbortController()
loading.value = true
error.value = null
try {
const response = await fetch(url, {
...options,
signal: controller.signal
})
data.value = await response.json()
} catch (err) {
if (err.name !== 'AbortError') {
error.value = err
}
} finally {
loading.value = false
}
}
const abort = () => {
controller?.abort()
}
// 组件卸载时自动取消
onBeforeUnmount(() => {
controller?.abort()
})
return { data, loading, error, execute, abort }
}
// 使用
const { data, loading, execute, abort } = useAbortableFetch()
await execute('/api/users')
✅ 3. 配合防抖使用
import { debounce } from 'lodash-es'
let controller: AbortController | null = null
const searchAPI = async (keyword: string) => {
controller?.abort()
controller = new AbortController()
const response = await fetch(`/api/search?q=${keyword}`, {
signal: controller.signal
})
return response.json()
}
// 防抖 + 取消旧请求
const handleSearch = debounce(async (keyword: string) => {
try {
const results = await searchAPI(keyword)
displayResults(results)
} catch (error) {
if (error.name !== 'AbortError') {
console.error(error)
}
}
}, 300)
✅ 4. 添加取消原因(Chrome 98+)
const controller = new AbortController()
// 可以传递取消原因
controller.abort('用户主动取消')
// 或传递错误对象
controller.abort(new Error('超时'))
// 捕获时可以获取原因
try {
await fetch(url, { signal: controller.signal })
} catch (error) {
if (error.name === 'AbortError') {
console.log('取消原因:', controller.signal.reason)
}
}
五、注意事项和陷阱
❌ 陷阱 1:重复使用 controller
// ❌ 错误:controller 只能使用一次
const controller = new AbortController()
controller.abort()
fetch('/api/1', { signal: controller.signal }) // 立即被取消
fetch('/api/2', { signal: controller.signal }) // 也会立即被取消
// ✅ 正确:每次都创建新的
let controller = new AbortController()
fetch('/api/1', { signal: controller.signal })
controller = new AbortController() // 创建新的
fetch('/api/2', { signal: controller.signal })
❌ 陷阱 2:忘记处理 AbortError
// ❌ 错误:会把正常的取消当作错误处理
try {
await fetch(url, { signal })
} catch (error) {
showErrorToast('请求失败') // 用户取消也会显示错误
}
// ✅ 正确:区分取消和错误
try {
await fetch(url, { signal })
} catch (error) {
if (error.name !== 'AbortError') {
showErrorToast('请求失败')
}
}
❌ 陷阱 3:忘记清理
// ❌ 可能导致内存泄漏
const controller = new AbortController()
controller.signal.addEventListener('abort', heavyCallback)
// ✅ 清理监听器
const controller = new AbortController()
const handler = () => console.log('aborted')
controller.signal.addEventListener('abort', handler)
// 不再需要时
controller.signal.removeEventListener('abort', handler)
六、兼容性处理
// 检查浏览器支持
if ('AbortController' in window) {
const controller = new AbortController()
// 使用 AbortController
} else {
// 降级方案
console.warn('浏览器不支持 AbortController')
}
// 或使用 polyfill
// npm install abortcontroller-polyfill
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
七、总结速查表
| 场景 | 代码模式 |
|---|---|
| 取消搜索 | controller?.abort() 后创建新 controller |
| 超时控制 | setTimeout(() => controller.abort(), 5000) |
| 组件卸载 | onBeforeUnmount(() => controller?.abort()) |
| 手动取消 | 按钮绑定 controller.abort() |
| 批量取消 | 多个请求共享同一个 signal |
| 错误处理 | if (error.name !== 'AbortError') |
核心原则:一次性使用,用完即弃,每次新请求都创建新的 AbortController。

浙公网安备 33010602011771号