作为前端架构师,封装axios需要考虑以下几个关键方面:
- 基础封装类:
class HttpClient {
constructor(config = {}) {
// 创建axios实例
this.instance = axios.create({
baseURL: config.baseURL || process.env.API_BASE_URL,
timeout: config.timeout || 30000,
withCredentials: true,
...config
})
// 初始化拦截器
this.initInterceptors()
// 保存pending请求
this.pendingRequests = new Map()
}
// 初始化拦截器
initInterceptors() {
// 请求拦截
this.instance.interceptors.request.use(
(config) => {
// 防重复请求
this.removePendingRequest(config)
this.addPendingRequest(config)
// 添加token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// 添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
}
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截
this.instance.interceptors.response.use(
(response) => {
// 移除完成的请求
this.removePendingRequest(response.config)
// 统一处理响应
return this.handleResponse(response)
},
(error) => {
// 移除失败的请求
error.config && this.removePendingRequest(error.config)
// 统一错误处理
return this.handleError(error)
}
)
}
// 处理响应数据
handleResponse(response) {
const res = response.data
// 假设后端返回格式为 { code, data, message }
if (res.code === 0) {
return res.data
}
// 处理特定错误码
switch (res.code) {
case 401:
this.handleUnauthorized()
break
case 403:
this.handleForbidden()
break
// ... 其他状态码处理
}
return Promise.reject(new Error(res.message))
}
// 统一错误处理
handleError(error) {
// 处理请求取消
if (axios.isCancel(error)) {
return Promise.reject(new Error('Request cancelled'))
}
// 处理超时
if (error.code === 'ECONNABORTED') {
return Promise.reject(new Error('Request timeout'))
}
// 处理网络错误
if (!error.response) {
return Promise.reject(new Error('Network error'))
}
// 处理HTTP状态码
const status = error.response.status
switch (status) {
case 401:
this.handleUnauthorized()
break
case 403:
this.handleForbidden()
break
case 404:
return Promise.reject(new Error('Resource not found'))
case 500:
return Promise.reject(new Error('Server error'))
default:
return Promise.reject(error)
}
}
// 防重复请求处理
generateRequestKey(config) {
const { method, url, params, data } = config
return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}
addPendingRequest(config) {
const requestKey = this.generateRequestKey(config)
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!this.pendingRequests.has(requestKey)) {
this.pendingRequests.set(requestKey, cancel)
}
})
}
removePendingRequest(config) {
const requestKey = this.generateRequestKey(config)
if (this.pendingRequests.has(requestKey)) {
const cancel = this.pendingRequests.get(requestKey)
cancel()
this.pendingRequests.delete(requestKey)
}
}
// 请求方法封装
async request(config) {
try {
const response = await this.instance.request(config)
return response
} catch (error) {
return Promise.reject(error)
}
}
async get(url, params = {}, config = {}) {
return this.request({
method: 'get',
url,
params,
...config
})
}
async post(url, data = {}, config = {}) {
return this.request({
method: 'post',
url,
data,
...config
})
}
// ... 其他HTTP方法
}
- 请求重试机制:
class RetryHttpClient extends HttpClient {
constructor(config = {}) {
super(config)
this.retryTimes = config.retryTimes || 3
this.retryDelay = config.retryDelay || 1000
}
async request(config) {
const retryConfig = {
retryTimes: config.retryTimes || this.retryTimes,
retryDelay: config.retryDelay || this.retryDelay,
currentRetry: 0
}
const requestWithRetry = async () => {
try {
return await super.request(config)
} catch (error) {
if (this.shouldRetry(error, retryConfig)) {
retryConfig.currentRetry++
await this.sleep(retryConfig.retryDelay)
return requestWithRetry()
}
throw error
}
}
return requestWithRetry()
}
shouldRetry(error, config) {
return (
config.currentRetry < config.retryTimes &&
!axios.isCancel(error) &&
error.response?.status >= 500
)
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
- 缓存处理:
class CacheHttpClient extends HttpClient {
constructor(config = {}) {
super(config)
this.cache = new Map()
this.cacheTime = config.cacheTime || 5 * 60 * 1000 // 默认5分钟
}
async get(url, params = {}, config = {}) {
if (config.useCache) {
const cacheKey = this.getCacheKey(url, params)
const cachedData = this.getCache(cacheKey)
if (cachedData) {
return cachedData
}
const response = await super.get(url, params, config)
this.setCache(cacheKey, response, config.cacheTime)
return response
}
return super.get(url, params, config)
}
getCacheKey(url, params) {
return `${url}?${JSON.stringify(params)}`
}
getCache(key) {
const cached = this.cache.get(key)
if (!cached) return null
if (Date.now() - cached.timestamp > cached.cacheTime) {
this.cache.delete(key)
return null
}
return cached.data
}
setCache(key, data, cacheTime = this.cacheTime) {
this.cache.set(key, {
data,
timestamp: Date.now(),
cacheTime
})
}
clearCache(url) {
if (url) {
const keys = Array.from(this.cache.keys())
keys.forEach(key => {
if (key.startsWith(url)) {
this.cache.delete(key)
}
})
} else {
this.cache.clear()
}
}
}
- 请求队列管理:
class QueueHttpClient extends HttpClient {
constructor(config = {}) {
super(config)
this.queue = []
this.maxConcurrent = config.maxConcurrent || 5
this.processing = 0
}
async request(config) {
if (this.processing >= this.maxConcurrent) {
await new Promise(resolve => {
this.queue.push(resolve)
})
}
this.processing++
try {
const response = await super.request(config)
return response
} finally {
this.processing--
if (this.queue.length > 0) {
const next = this.queue.shift()
next()
}
}
}
}
- 使用示例:
// 创建实例
const http = new CacheHttpClient({
baseURL: 'https://api.example.com',
timeout: 30000,
retryTimes: 3,
maxConcurrent: 5
})
// API封装示例
const userApi = {
async getUserInfo(userId) {
return http.get(`/user/${userId}`, {}, { useCache: true })
},
async updateUser(userId, data) {
return http.post(`/user/${userId}`, data)
},
async uploadAvatar(file) {
const formData = new FormData()
formData.append('file', file)
return http.post('/user/avatar', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
}
// 使用示例
try {
const userInfo = await userApi.getUserInfo(123)
console.log(userInfo)
} catch (error) {
console.error('Failed to get user info:', error)
}
- 最佳实践和注意事项:
- 统一状态码和错误处理
- 请求/响应数据转换
- 取消请求处理
- 防重复请求
- 请求超时处理
- 刷新token机制
- 日志记录
- 性能监控
- 请求队列管理
- 缓存策略
- 重试机制
- 并发控制
通过这样的封装,可以实现:
- 统一的接口规范
- 统一的错误处理
- 更好的代码复用
- 更容易的维护和扩展
- 更好的性能优化
- 更好的用户体验
评论区