1. Axios 简介
Axios 是一个基于 Promise 的 HTTP 客户端,主要用于浏览器和 Node.js 中进行 HTTP 请求。它支持拦截请求和响应、取消请求、自动转换 JSON 数据等功能。
2. 封装 Axios 实例
为了便于在项目中统一处理请求配置,可以创建一个封装后的 Axios 实例,并在其中设置默认的 baseURL、超时、请求头等参数。
3.取消请求
Axios 使用 AbortController 来实现请求取消功能。其核心机制是:
-
为每个请求创建一个
AbortController实例 -
将控制器的
signal附加到请求配置中 -
需要取消时调用控制器的
abort()方法
import type { AxiosInstance, AxiosRequestConfig, CreateAxiosDefaults } from 'axios'
import axios from 'axios'
class RequestManager {
instance: AxiosInstance // Axios 实例
pendingRequests: Map<string, any> // 进行中的请求
completedRequests: Map<string, any> // 已完成的请求
cancelledRequests: Map<string, any> // 已取消的请求
globalDebounce: boolean // 全局防抖开关
requestCounter: number // 请求计数器
constructor(config?: CreateAxiosDefaults) {
// 初始化 Axios 实例
this.instance = axios.create(config)
// 存储所有请求
this.pendingRequests = new Map() // 进行中的请求
this.completedRequests = new Map() // 已完成的请求
this.cancelledRequests = new Map() // 已取消的请求
// 配置选项
this.globalDebounce = true // 全局防抖开关
this.requestCounter = 0 // 请求计数器
// 初始化拦截器
this.setupInterceptors()
}
/**
* 生成请求唯一标识
* @param {object} config Axios 请求配置
* @returns {string} 唯一标识
*/
static generateRequestKey(config: AxiosRequestConfig) {
const { method, url, params, data } = config
return `${method}-${url}-${JSON.stringify(params)}-${JSON.stringify(data)}`
}
/**
* 添加请求到待处理队列
* @param {object} config Axios 请求配置
* @returns {string} 生成的请求ID
*/
addPendingRequest(config: AxiosRequestConfig, debounce = true) {
const requestId = `REQ-${++this.requestCounter}`
// 如果防抖开启,检查是否有重复请求
if (debounce ?? this.globalDebounce) {
const requestKey = RequestManager.generateRequestKey(config)
// 取消已存在的相同请求
for (const [id, request] of this.pendingRequests) {
if (RequestManager.generateRequestKey(request.config) === requestKey) {
this.cancelRequest(id, `取消重复请求: ${id}`)
}
}
}
// 创建新的 AbortController
const controller = new AbortController()
// 存储请求信息
this.pendingRequests.set(requestId, {
id: requestId,
config,
controller,
status: 'pending',
startTime: Date.now(),
endTime: null,
})
return requestId
}
/**
* 完成请求(从pending移动到completed)
* @param {string} requestId 请求ID
*/
completeRequest(requestId: string) {
if (this.pendingRequests.has(requestId)) {
const request = this.pendingRequests.get(requestId)
request.status = 'completed'
request.endTime = Date.now()
this.pendingRequests.delete(requestId)
this.completedRequests.set(requestId, request)
}
}
/**
* 取消请求
* @param {string} requestId 请求ID
* @param {string} [reason] 取消原因
* @returns {boolean} 是否取消成功
*/
cancelRequest(requestId: string, reason = '手动取消请求') {
if (this.pendingRequests.has(requestId)) {
const request = this.pendingRequests.get(requestId)
request.controller.abort(reason)
request.status = 'cancelled'
request.endTime = Date.now()
request.cancelReason = reason
this.pendingRequests.delete(requestId)
this.cancelledRequests.set(requestId, request)
return true
}
return false
}
/**
* 取消所有进行中的请求
* @param {string} [reason] 取消原因
*/
cancelAllRequests(reason = '取消所有请求') {
this.pendingRequests.forEach((request, requestId) => {
this.cancelRequest(requestId, reason)
})
}
/**
* 设置请求防抖开关
* @param {boolean} enabled 是否启用
*/
setDebounceEnabled(enabled: boolean) {
this.globalDebounce = enabled
}
/**
* 发起请求并管理请求队列
* @template T 响应数据类型
* @param config Axios请求配置
* @param opts 可选参数
* @param opts.callback 请求开始时的回调函数,接收请求ID
* @param opts.debounce 是否启用防抖,默认为true
* @returns 返回Promise包装的响应数据
* @throws 当请求失败时抛出错误
*/
request<T>(config: AxiosRequestConfig, opts?: { callback?: (requestId: string) => void, debounce?: boolean }) {
const { callback, debounce = true } = opts || {}
// 添加请求到队列并获取ID
const requestId = this.addPendingRequest(config, debounce)
// 执行回调返回requestId
if (typeof callback === 'function') {
callback?.(requestId)
}
// 设置中断信号
config.signal = this.pendingRequests.get(requestId).controller.signal
// 发起请求
return this.instance(config)
.then((response) => {
this.completeRequest(requestId)
return response
})
.catch((error) => {
if (axios.isCancel(error)) {
// 请求被取消,不改变状态(已在cancelRequest中处理)
}
else {
this.completeRequest(requestId)
}
throw error
}) as Promise<T>
}
/**
* 初始化axios拦截器
*/
setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
// 可以在这里统一处理config
return config
},
error => Promise.reject(error),
)
// 响应拦截器
this.instance.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
if (axios.isCancel(error)) {
// 请求被取消
}
return Promise.reject(error)
},
)
}
/**
* 获取请求状态
* @param {string} requestId 请求ID
* @returns {object | null} 请求状态信息
*/
getRequestStatus(requestId: string) {
if (this.pendingRequests.has(requestId)) {
return this.pendingRequests.get(requestId)
}
if (this.completedRequests.has(requestId)) {
return this.completedRequests.get(requestId)
}
if (this.cancelledRequests.has(requestId)) {
return this.cancelledRequests.get(requestId)
}
return null
}
/**
* 获取所有请求统计
* @returns {object} 统计信息
*/
getStats() {
return {
total: this.requestCounter,
pending: this.pendingRequests.size,
completed: this.completedRequests.size,
cancelled: this.cancelledRequests.size,
}
}
}
export type { AxiosInstance, AxiosRequestConfig, CreateAxiosDefaults }
export default RequestManager
总结
通过封装 Axios 实例并实现防抖逻辑,可以在项目中更有效地管理 HTTP 请求,减少不必要的网络流量和性能开销。根据项目需求,还可以灵活配置防抖时间以及控制何时立即执行请求,确保用户体验与系统性能的平衡。