Axios 二次封装高级教程(含请求取消等功能)
整理不易,收藏、点赞、关注哦!
一、总体架构设计
目的:构建一套功能完善、易用且健壮的 HTTP 请求封装层
核心功能:
- 请求拦截、响应拦截
- 请求取消(防止重复请求、接口防抖)
- 请求合并与批量处理
- 缓存策略(内存缓存、localStorage、sessionStorage)
- 失败重试机制
- 上传下载进度监听
- 状态化 hooks 集成(loading、error、data 管理)
- 统一接口调用管理
二、Axios 实例创建与基础配置
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, Canceler } from 'axios'
const pendingRequestMap = new Map<string, Canceler>()
// 生成请求唯一key,便于取消重复请求
function getRequestKey(config: AxiosRequestConfig): string {
const { method, url, params, data } = config
return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}
// 添加请求到pending队列
function addPendingRequest(config: AxiosRequestConfig) {
const requestKey = getRequestKey(config)
if (pendingRequestMap.has(requestKey)) {
// 取消重复请求
pendingRequestMap.get(requestKey)?.('取消重复请求')
}
config.cancelToken = new axios.CancelToken((cancel) => {
pendingRequestMap.set(requestKey, cancel)
})
}
// 移除请求
function removePendingRequest(config: AxiosRequestConfig) {
const requestKey = getRequestKey(config)
if (pendingRequestMap.has(requestKey)) {
pendingRequestMap.delete(requestKey)
}
}
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 15000,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
removePendingRequest(config) // 移除之前的请求
addPendingRequest(config) // 添加当前请求
// 这里可以加token等鉴权信息
// const token = localStorage.getItem('token')
// if (token) config.headers.Authorization = `Bearer ${token}`
return config
},
(error) => Promise.reject(error)
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
removePendingRequest(response.config)
// 统一处理状态码
const { data } = response
if (data.code !== 0) {
// 自定义错误处理
return Promise.reject(new Error(data.message || 'Error'))
}
return data
},
(error) => {
if (axios.isCancel(error)) {
console.warn('请求被取消:', error.message)
return Promise.reject({ canceled: true })
}
return Promise.reject(error)
}
)
export default service
三、请求取消与防抖(防重复请求)
- 场景:用户快速重复点击按钮,多次发送相同请求
- 解决:通过
cancelToken
取消前一个重复请求,保证接口调用稳定
核心逻辑已在上面pendingRequestMap
实现,结合唯一请求Key判断。
四、多请求合并(批量接口请求)
/**
* 批量请求示例
* @param urls 请求地址数组
*/
function batchRequest(urls: string[]) {
const requests = urls.map((url) => service.get(url))
return axios.all(requests).then(axios.spread((...responses) => responses))
}
// 使用
batchRequest(['/api/a', '/api/b', '/api/c']).then(([resA, resB, resC]) => {
console.log(resA, resB, resC)
})
五、缓存策略(内存缓存 + 本地缓存)
const cacheMap = new Map<string, any>()
// 简单缓存示例
function requestWithCache<T = any>(config: AxiosRequestConfig, useCache = true): Promise<T> {
const key = getRequestKey(config)
if (useCache && cacheMap.has(key)) {
return Promise.resolve(cacheMap.get(key))
}
return service.request<T>(config).then((res) => {
cacheMap.set(key, res)
return res
})
}
- 可根据业务需求,扩展使用
localStorage
或sessionStorage
缓存,并设置过期时间。
六、自动失败重试机制
interface RetryConfig extends AxiosRequestConfig {
retry?: number // 重试次数
retryDelay?: number // 重试间隔ms
}
function retryRequest(config: RetryConfig): Promise<any> {
const { retry = 3, retryDelay = 1000 } = config
let currentRetry = 0
const request = (): Promise<any> => {
return service.request(config).catch((error) => {
if (currentRetry < retry) {
currentRetry++
return new Promise((resolve) => setTimeout(resolve, retryDelay)).then(request)
}
return Promise.reject(error)
})
}
return request()
}
- 可以在请求层或调用层根据需求灵活调用。
七、上传和下载进度监听
// 上传进度
function uploadFile(url: string, file: File, onProgress: (percent: number) => void) {
const formData = new FormData()
formData.append('file', file)
return service.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
onProgress(percent)
},
})
}
// 下载进度
function downloadFile(url: string, onProgress: (percent: number) => void) {
return service.get(url, {
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
onProgress(percent)
},
})
}
八、封装状态式 Hook(结合 Vue 3 Composition API)
import { ref } from 'vue'
interface UseRequestOptions {
manual?: boolean // 是否手动调用
retry?: number
retryDelay?: number
}
export function useRequest<T = any>(
requestFn: (...args: any[]) => Promise<T>,
options: UseRequestOptions = {}
) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const fetch = async (...args: any[]) => {
loading.value = true
error.value = null
try {
data.value = await requestFn(...args)
loading.value = false
return data.value
} catch (err: any) {
error.value = err
loading.value = false
throw err
}
}
if (!options.manual) {
fetch()
}
return { data, loading, error, fetch }
}
- 使用示例:
import { useRequest } from './useRequest'
import service from './axios'
const { data, loading, error, fetch } = useRequest(() => service.get('/api/user'))
九、文件上传并发送给后端(结合FileReader)
function uploadFileWithPreview(file: File, url: string) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file) // 读取文件为base64编码
reader.onload = () => {
// 可以先预览file的base64,也可以直接上传二进制
const formData = new FormData()
formData.append('file', file) // 直接上传文件
service
.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(resolve)
.catch(reject)
}
reader.onerror = (err) => reject(err)
})
}
十、总结
- 请求取消通过唯一请求key + CancelToken实现,防止重复请求。
- 多请求合并使用
axios.all
和axios.spread
实现批量请求并行。 - 缓存策略基于请求key设计内存或本地缓存,提升请求效率。
- 自动重试可根据失败情况自动重试,增强请求鲁棒性。
- 上传/下载进度监听灵活支持文件上传和下载的进度反馈。
- 状态式 Hook结合 Vue 3 Composition API,实现简洁易用的请求状态管理。
- 文件上传可结合 FileReader 实现预览和上传双功能。