XHR / Fetch / Axios 请求的取消请求与请求重试是前端性能优化与稳定性处理的重点,也是面试高频内容。下面是这三种方式的详解封装方案(可直接复用)。
✅ 一、Axios 取消请求与请求重试封装
1. 安装依赖(可选,用于扩展)
npm install axios
2. 封装 cancelToken 与 retry 的 axios 请求模块
// axiosRequest.js
import axios from 'axios'
const pendingMap = new Map()
// 生成唯一 key(用于标记请求)
function getRequestKey(config) {
const { method, url, params, data } = config
return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}
// 添加请求到 pendingMap
function addPending(config) {
const key = getRequestKey(config)
config.cancelToken = new axios.CancelToken(cancel => {
if (!pendingMap.has(key)) {
pendingMap.set(key, cancel)
}
})
}
// 移除请求
function removePending(config) {
const key = getRequestKey(config)
if (pendingMap.has(key)) {
const cancel = pendingMap.get(key)
cancel && cancel()
pendingMap.delete(key)
}
}
// 重试机制封装
function retryAdapterEnhancer(adapter, options = {}) {
const { retries = 3, delay = 1000 } = options
return async config => {
let retryCount = 0
const request = async () => {
try {
return await adapter(config)
} catch (err) {
if (retryCount < retries) {
retryCount++
await new Promise(res => setTimeout(res, delay))
return request()
} else {
return Promise.reject(err)
}
}
}
return request()
}
}
// 创建实例
const axiosInstance = axios.create({
timeout: 5000,
adapter: retryAdapterEnhancer(axios.defaults.adapter, { retries: 2, delay: 1000 })
})
// 请求拦截器
axiosInstance.interceptors.request.use(config => {
removePending(config)
addPending(config)
return config
})
// 响应拦截器
axiosInstance.interceptors.response.use(
response => {
removePending(response.config)
return response
},
error => {
if (axios.isCancel(error)) {
console.warn('Request canceled:', error.message)
}
return Promise.reject(error)
}
)
export default axiosInstance
✅ 使用示例:
import request from './axiosRequest'
request.get('/api/data', { params: { id: 1 } })
.then(res => console.log(res))
.catch(err => console.error(err))
✅ 二、Fetch 封装(支持取消请求 & 重试)
1. 使用 AbortController 封装取消与重试
// fetchRequest.js
export function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) {
const controller = new AbortController()
options.signal = controller.signal
const fetchData = (retryCount = 0) => {
return fetch(url, options).then(res => {
if (!res.ok) throw new Error('Network response was not ok')
return res
}).catch(err => {
if (retryCount < retries && err.name !== 'AbortError') {
return new Promise(resolve =>
setTimeout(() => resolve(fetchData(retryCount + 1)), delay)
)
}
throw err
})
}
return {
promise: fetchData(),
cancel: () => controller.abort()
}
}
✅ 使用示例:
const { promise, cancel } = fetchWithRetry('/api/data', {}, 2, 1000)
promise
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err))
// 调用 cancel() 可随时取消请求
✅ 三、XHR 原生封装(带取消 & 重试)
// xhrRequest.js
export function xhrWithRetry({ url, method = 'GET', data = null, retries = 3, delay = 1000 }) {
let xhr = null
const send = (retryCount = 0, resolve, reject) => {
xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText)
} else if (retryCount < retries) {
setTimeout(() => send(retryCount + 1, resolve, reject), delay)
} else {
reject(new Error(`Request failed: ${xhr.status}`))
}
}
}
xhr.onerror = () => reject(new Error('Network Error'))
xhr.send(data)
}
const promise = new Promise((resolve, reject) => send(0, resolve, reject))
return {
promise,
cancel: () => xhr && xhr.abort()
}
}
✅ 使用示例:
const { promise, cancel } = xhrWithRetry({ url: '/api/data' })
promise.then(res => console.log(res)).catch(err => console.error(err))
// 可随时调用取消
cancel()
✅ 总结对比
特性/方式 | 取消支持 | 重试支持 | 易用性 | 推荐场景 |
---|---|---|---|---|
Axios | ✔️ CancelToken | ✔️ 自定义 adapter | ✅ 最佳 | Vue/React 项目 |
Fetch | ✔️ AbortController | ✔️ 手动封装 | ✅ 清晰 | 原生 / SSR 项目 |
XHR | ✔️ abort() | ✔️ 手动封装 | ❌ 复杂 | 老旧兼容需求 |
如果你想把这三套方案整合成一个通用库或 TypeScript 模块,我也可以帮你封装成统一接口版本。是否需要我继续处理这部分?