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 } = configreturn [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 } = optionsreturn async config => {let retryCount = 0const 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.signalconst 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 = nullconst 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 模塊,我也可以幫你封裝成統一接口版本。是否需要我繼續處理這部分?