全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-08-15_我将封装史上最优雅的 Axios

您的位置:首页 >> 新闻 >> 行业资讯

我将封装史上最优雅的 Axios (金石瓜分计划强势上线,速戳上图了解详情) ?经过一上午的反复打磨,终于写出了这个让我满意的 HTTP 客户端封装。当我把代码发给朋友时,他竟然挑出了一堆问题!还好我都一一解决了。 ??? 为什么要重新封装 Axios?我们都知道 Axios 是 JavaScript 世界里最受欢迎的 HTTP 客户端,但在实际项目中,我们总是需要: ??「统一的错误处理」- 不想在每个请求里写重复的 try-catch??「请求取消机制」- 用户快速切换页面时取消无用请求??「自动重试功能」- 网络不稳定时自动重试??「TypeScript 完美支持」- 类型安全,IDE 智能提示???「多实例管理」- 不同 API 服务需要不同配置市面上的封装要么太简单,要么太复杂。所以我决定写一个「既优雅又实用」的版本。 ?? 设计理念这个封装的设计遵循几个核心原则: 「?? 渐进式增强」- 可以像原生 Axios 一样简单使用,也可以启用高级功能「?? 类型安全」- 完整的 TypeScript 支持,编译时发现问题「?? 灵活扩展」- 支持多实例、自定义拦截器、业务定制「? 性能优先」- 自动去重、智能重试、内存管理「?? 文档友好」- 丰富的示例和注释,上手即用?? 完整源码importAxios, {typeAxiosInstance,typeAxiosRequestConfig,typeCustomParamsSerializer,typeAxiosResponse,typeInternalAxiosRequestConfig,typeMethod,typeAxiosError }from"axios"; import{ stringify }from"qs"; // 基础配置 constdefaultConfig: AxiosRequestConfig = { timeout:6000, headers: { "Content-Type":"application/json;charset=utf-8" }, paramsSerializer: { serialize: stringifyasunknownasCustomParamsSerializer } }; // 响应数据基础结构 exportinterfaceBaseResponse { code:number; message?:string; } // 去除与BaseResponse冲突的字段 typeOmitBaseResponse = OmitT, keyof BaseResponse // 响应数据类型定义 - 避免属性冲突,确保BaseResponse优先级 exporttypeResponseDataT =any = BaseResponse & OmitBaseResponse // 响应数据验证函数类型 exporttypeResponseValidatorT =any =(data: ResponseData) =boolean; // 重试配置 exportinterfaceRetryConfig { retries?:number;// 重试次数 retryDelay?:number;// 重试延迟(毫秒) retryCondition?:(error: AxiosError) =boolean;// 重试条件 } // 拦截器配置类型 interfaceInterceptorsConfig { requestInterceptor?:(config: InternalAxiosRequestConfig) =InternalAxiosRequestConfig; requestErrorInterceptor?:(error: AxiosError) =Promiseany responseInterceptor?:(response: AxiosResponseResponseDataany) =any; responseErrorInterceptor?:(error: AxiosError) =Promiseany } // 请求唯一键 typeRequestKey =string| symbol; /** * 增强型 HTTP 客户端,基于 Axios 封装 * 支持拦截器配置、请求取消、多实例管理等功能 */ classHttpClient { privateinstance: AxiosInstance; privaterequestInterceptorId?:number; privateresponseInterceptorId?:number; privateabortControllers: MapRequestKey, AbortController =newMap(); /** * 创建 HTTP 客户端实例 * @param customConfig 自定义 Axios 配置 * @param interceptors 自定义拦截器配置 */ constructor(customConfig?: AxiosRequestConfig, interceptors?: InterceptorsConfig) { this.instance = Axios.create({ ...defaultConfig, ...customConfig }); this.initInterceptors(interceptors); } /** 初始化拦截器 */ privateinitInterceptors(interceptors?: InterceptorsConfig):void{ this.initRequestInterceptor(interceptors?.requestInterceptor, interceptors?.requestErrorInterceptor); this.initResponseInterceptor(interceptors?.responseInterceptor, interceptors?.responseErrorInterceptor); } /** 初始化请求拦截器 */ privateinitRequestInterceptor(customInterceptor?: InterceptorsConfig["requestInterceptor"], customErrorInterceptor?: InterceptorsConfig["requestErrorInterceptor"]):void{ // 默认请求拦截器 constdefaultInterceptor = (config: InternalAxiosRequestConfig):InternalAxiosRequestConfig={ /* 在这里写请求拦截器的默认业务逻辑 */ // 示例: 添加token // const token = localStorage.getItem('token'); // if (token) { // config.headers.Authorization = `Bearer ${token}`; // } // 示例: 添加时间戳防止缓存 // if (config.method?.toUpperCase() === 'GET') { // config.params = { ...config.params, _t: Date.now() }; // } returnconfig; // 默认请求错误拦截器 constdefaultErrorInterceptor = (error: AxiosError):Promiseany { /* 在这里写请求错误拦截器的默认业务逻辑 */ // 示例: 处理请求前的错误 // console.error('请求配置错误:', error); returnPromise.reject(error); // 优先使用自定义拦截器,否则使用默认拦截器 this.requestInterceptorId =this.instance.interceptors.request.use(customInterceptor || defaultInterceptor, customErrorInterceptor || defaultErrorInterceptor); } /** 初始化响应拦截器 */ privateinitResponseInterceptor(customInterceptor?: InterceptorsConfig["responseInterceptor"], customErrorInterceptor?: InterceptorsConfig["responseErrorInterceptor"]):void{ // 默认响应拦截器 constdefaultInterceptor =(response: AxiosResponseResponseDataany) ={ constrequestKey =this.getRequestKey(response.config); if(requestKey)this.abortControllers.delete(requestKey); /* 在这里写响应拦截器的默认业务逻辑 */ // 示例: 处理不同的响应码 // const { code, message } = response.data; // switch(code) { // case 200: // return response.data; // case 401: // // 未授权处理 // break; // case 403: // // 权限不足处理 // break; // default: // // 其他错误处理 // console.error('请求错误:', message); // } returnresponse.data; // 默认响应错误拦截器 constdefaultErrorInterceptor = (error: AxiosError):Promiseany { if(error.config) { constrequestKey =this.getRequestKey(error.config); if(requestKey)this.abortControllers.delete(requestKey); } // 处理请求被取消的情况 if(Axios.isCancel(error)) { console.warn("请求已被取消:", error.message); returnPromise.reject(newError("请求已被取消")); } // 网络错误处理 if(!(errorasAxiosError).response) { if((errorasany).code ==="ECONNABORTED"|| (errorasAxiosError).message?.includes("timeout")) { returnPromise.reject(newError("请求超时,请稍后重试")); } returnPromise.reject(newError("网络错误,请检查网络连接")); } // HTTP状态码错误处理 conststatus = (errorasAxiosError).response?.status; constcommonErrors: Recordnumber,string = { 400:"请求参数错误", 401:"未授权,请重新登录", 403:"权限不足", 404:"请求的资源不存在", 408:"请求超时", 500:"服务器内部错误", 502:"网关错误", 503:"服务暂不可用", 504:"网关超时" constmessage = commonErrors[status] ||`请求失败(状态码:${status})`; returnPromise.reject(newError(message)); // 优先使用自定义拦截器,否则使用默认拦截器 this.responseInterceptorId =this.instance.interceptors.response.use(customInterceptor || defaultInterceptor, customErrorInterceptor || defaultErrorInterceptor); } /** 生成请求唯一标识 */ privategetRequestKey(config: AxiosRequestConfig): RequestKey |undefined{ if(!config.url)returnundefined; return`${config.method?.toUpperCase()}-${config.url}`; } /** 设置取消控制器 - 用于取消重复请求或主动取消请求 */ privatesetupCancelController(config: AxiosRequestConfig, requestKey?: RequestKey): AxiosRequestConfig { constkey = requestKey ||this.getRequestKey(config); if(!key)returnconfig; // 如果已有相同key的请求,先取消它 this.cancelRequest(key); constcontroller =newAbortController(); this.abortControllers.set(key, controller); return{ ...config, signal: controller.signal } /** 移除请求拦截器 */ publicremoveRequestInterceptor():void{ if(this.requestInterceptorId !==undefined) { this.instance.interceptors.request.eject(this.requestInterceptorId); this.requestInterceptorId =undefined;// 重置ID,避免重复移除 } } /** 移除响应拦截器 */ publicremoveResponseInterceptor():void{ if(this.responseInterceptorId !==undefined) { this.instance.interceptors.response.eject(this.responseInterceptorId); this.responseInterceptorId =undefined;// 重置ID,避免重复移除 } } /** 动态设置请求拦截器 */ publicsetRequestInterceptor(customInterceptor?: InterceptorsConfig["requestInterceptor"], customErrorInterceptor?: InterceptorsConfig["requestErrorInterceptor"]):void{ this.removeRequestInterceptor(); this.initRequestInterceptor(customInterceptor, customErrorInterceptor); } /** 动态设置响应拦截器 */ publicsetResponseInterceptor(customInterceptor?: InterceptorsConfig["responseInterceptor"], customErrorInterceptor?: InterceptorsConfig["responseErrorInterceptor"]):void{ this.removeResponseInterceptor(); this.initResponseInterceptor(customInterceptor, customErrorInterceptor); } /** 获取 Axios 实例 */ publicgetInstance(): AxiosInstance { returnthis.instance; } /** * 取消某个请求 * @param key 请求唯一标识 * @param message 取消原因 * @returns 是否成功取消 */ publiccancelRequest(key: RequestKey, message?:string):boolean{ constcontroller =this.abortControllers.get(key); if(controller) { controller.abort(message ||`取消请求:${String(key)}`); this.abortControllers.delete(key); returntrue; } returnfalse; } /** * 取消所有请求 * @param message 取消原因 */ publiccancelAllRequests(message?:string):void{ this.abortControllers.forEach((controller, key) ={ controller.abort(message ||`取消所有请求:${String(key)}`); this.abortControllers.clear(); } /** * 判断是否为取消错误 * @param error 错误对象 * @returns 是否为取消错误 */ publicstaticisCancel(error: unknown):boolean{ returnAxios.isCancel(error); } /** * 睡眠函数 * @param ms 毫秒数 */ privatesleep(ms:number):Promisevoid { returnnewPromise(resolve=setTimeout(resolve, ms)); } /** * 通用请求方法 * @param method 请求方法 * @param url 请求地址 * @param config 请求配置 * @returns 响应数据 */ publicasyncrequestT =any(method: Method, url:string, config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }):PromiseResponseData { const{ requestKey, retry, ...restConfig } = config || {}; // 设置合理的默认重试条件 constdefaultRetryCondition =(error: AxiosError) ={ // 默认只重试网络错误或5xx服务器错误 return!error.response || (error.response.status =500&& error.response.status 600); constretryConfig = { retries:0, retryDelay:1000, retryCondition: defaultRetryCondition, ...retry letlastError:any; constkey = requestKey ||this.getRequestKey({ ...restConfig, method, url }); for(letattempt =0; attempt = retryConfig.retries; attempt++) { try{ // 重试前清除旧的AbortController(避免重试请求被误取消) if(attempt 0&& key) { this.abortControllers.delete(key); } constrequestConfig =this.setupCancelController({ ...restConfig, method, url }, requestKey); /* 在这里写通用请求前的业务逻辑 */ // 示例: 记录请求日志 // console.log(`[${method.toUpperCase()}] ${url}:`, restConfig); constresponse =awaitthis.instance.requestResponseData(requestConfig); /* 在这里写通用请求后的业务逻辑 */ // 示例: 记录响应日志 // console.log(`[${method.toUpperCase()}] ${url} 响应:`, response.data); returnresponse.data; }catch(error) { lastError = error; // 如果是最后一次尝试或不满足重试条件或请求被取消,直接抛出错误 if(attempt === retryConfig.retries || !retryConfig.retryCondition(errorasAxiosError) || HttpClient.isCancel(error)) { break; } // 延迟后重试 if(retryConfig.retryDelay 0) { awaitthis.sleep(retryConfig.retryDelay); } } } /* 在这里写请求异常的通用处理逻辑 */ // 示例: 统一错误提示 // if (lastError instanceof Error) { // console.error('请求失败:', lastError.message); // } returnPromise.reject(lastError); } /** * GET 请求 * @param url 请求地址 * @param config 请求配置 * @returns 响应数据 */ publicgetT =any(url:string, config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }):PromiseResponseData { returnthis.request("get", url, config); } /** * POST 请求 * @param url 请求地址 * @param data 请求数据 * @param config 请求配置 * @returns 响应数据 */ publicpostT =any(url:string, data?:any, config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }):PromiseResponseData { returnthis.request("post", url, { ...config, data }); } /** * PUT 请求 * @param url 请求地址 * @param data 请求数据 * @param config 请求配置 * @returns 响应数据 */ publicputT =any(url:string, data?:any, config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }):PromiseResponseData { returnthis.request("put", url, { ...config, data }); } /** * DELETE 请求 * @param url 请求地址 * @param config 请求配置 * @returns 响应数据 */ publicdeleteT =any(url:string, config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }):PromiseResponseData { returnthis.request("delete", url, config); } /** * PATCH 请求 * @param url 请求地址 * @param data 请求数据 * @param config 请求配置 * @returns 响应数据 */ publicpatchT =any(url:string, data?:any, config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }):PromiseResponseData { returnthis.request("patch", url, { ...config, data }); } } // 默认导出实例 - 可直接使用 exportconsthttp =newHttpClient(); exportdefaultHttpClient; ??? 使用指南1. 基础请求操作:GET/POST 等常用方法任何 HTTP 客户端最核心的功能都是处理基础请求,HttpClient 对常用的 HTTP 方法进行了友好封装,同时提供了完整的 TypeScript 类型支持。 首先定义我们需要用到的数据类型: // 用户信息接口 interfaceUser { id:number; name:string; email:string; } // 分页响应通用接口 interfacePageResponse { list: T[]; // 数据列表 total:number; // 总条数 page:number; // 当前页码 size:number; // 每页条数 } 基础请求操作示例: asyncfunctionbasicRequests(){ try{ // 1. GET 请求(带查询参数) constuserPage =awaithttp.getPageResponseUser('/api/users', { params: { page:1, size:10} console.log(`第${userPage.page}页,共${userPage.total}个用户:`, userPage.list); // 2. POST 请求(提交数据) constnewUser =awaithttp.post{ id:number}('/api/users', { name:'张三', email:'zhangsan@example.com' console.log('新增用户ID:', newUser.id); // 3. PUT 请求(更新数据) constupdatedUser =awaithttp.putUser('/api/users/1', { id:1, name:'张三三', email:'zhangsansan@example.com' // 4. DELETE 请求 constdeleteRes =awaithttp.delete{ success:boolean; message?:string}('/api/users/1'); console.log('删除结果:', deleteRes.success); }catch(error) { // 统一错误处理 console.error('请求失败:', errorinstanceofError? error.message : error); } } 「关键特性」: 泛型参数直接指定响应数据类型,获得完整的类型提示统一的错误处理机制,无需在每个请求中重复编写 try-catch自动处理请求参数序列化和响应数据解析2. 多实例管理:为不同 API 服务创建专属客户端在复杂项目中,我们常常需要与多个 API 服务交互,每个服务可能有不同的基础路径、超时设置或认证方式。HttpClient 支持创建多个独立实例,完美解决这个问题。 // 定义商品数据接口 interfaceProduct { id:string; title:string; price:number; stock:number; } // 1. 创建用户服务专用实例 constuserApi =newHttpClient({ baseURL:'https://api.example.com/user', // 用户服务基础路径 timeout:10000 // 超时设置为10秒 }); // 2. 创建商品服务专用实例(带特殊请求头) constproductApi =newHttpClient({ baseURL:'https://api.example.com/product', // 商品服务基础路径 headers: {'X-Product-Token':'special-token'} // 商品服务需要的特殊头部 }); // 使用多实例处理不同服务的请求 asyncfunctionuseMultiInstances(){ // 调用用户服务API constuserPage =awaituserApi.getPageResponseUser('/list'); // 调用商品服务API constproductPage =awaitproductApi.getPageResponseProduct('/list'); // 两个实例的配置完全隔离,不会相互影响 } 「适用场景」: 前后端分离项目中对接多个微服务同时需要访问内部API和第三方API不同接口有不同的超时需求(如普通接口5秒,文件上传60秒)3. 自定义拦截器:业务逻辑与请求处理的解耦拦截器是处理请求/响应公共逻辑的最佳方式,HttpClient 允许在初始化时配置自定义拦截器,将认证、日志等横切关注点与业务逻辑分离。 最常见的场景是处理认证逻辑: // 创建带权限验证的HTTP客户端实例 constauthHttp =newHttpClient( { baseURL:'https://api.example.com/auth'}, // 基础配置 { // 请求拦截器:添加认证Token requestInterceptor:(config) ={ consttoken = localStorage.getItem('token'); if(token) { config.headers.Authorization =`Bearer${token}`; } returnconfig; }, // 响应拦截器:处理认证失败 responseInterceptor:(response) ={ // 未授权,自动跳转到登录页 if(response.code ===401) { localStorage.removeItem('token'); window.location.href ='/login'; } returnresponse; } } ); 「拦截器的典型用途」: 添加全局认证信息(Token、API Key等)统一处理错误码(如401未授权、403权限不足)实现请求/响应日志记录添加请求时间戳防止缓存4. 动态修改拦截器:运行时灵活调整请求行为有时候我们需要在运行时根据业务场景动态改变请求/响应处理逻辑,HttpClient 提供了动态修改拦截器的能力。 // 定义日志数据接口 interfaceLogData { id:number; timestamp:string; content:string; } asyncfunctiondynamicInterceptors(){ // 场景1:临时添加日志拦截器 constlogInterceptor =(response: AxiosResponseResponseDataPageResponseLogData) ={ console.log(`请求[${response.config.url}]返回${response.data.total}条日志`); returnresponse; // 设置新的响应拦截器 http.setResponseInterceptor(logInterceptor); // 发送请求时会执行新的拦截器 awaithttp.getPageResponseLogData('/api/logs'); // 场景2:完成日志收集后,恢复默认拦截器 http.setResponseInterceptor(); // 场景3:动态更新认证信息(如Token刷新后) constnewToken ='new-auth-token'; http.setRequestInterceptor((config) ={ config.headers.Authorization =`Bearer${newToken}`; returnconfig; } 「实用场景」: 临时开启调试日志Token过期后动态更新认证信息特定页面需要特殊的请求头A/B测试时切换不同的API处理逻辑5. 请求取消:优化用户体验的关键技巧在用户快速操作或页面切换时,取消无用的请求可以显著提升性能和用户体验。HttpClient 提供了多种灵活的请求取消方式。 5.1 主动取消单个请求asyncfunctioncancelSingleRequest(){ constrequestKey ='user-list'; // 定义唯一标识 try{ // 发起请求时指定requestKey constpromise = http.getPageResponseUser('/api/users', { requestKey }); // 模拟:200ms后取消请求(例如用户快速切换了页面) setTimeout(()={ http.cancelRequest(requestKey,'数据已过时'); },200); constresult =awaitpromise; }catch(error) { // 判断是否为取消错误 if(HttpClient.isCancel(error)) { console.log('请求已取消:', error.message); } } } 5.2 自动取消重复请求asyncfunctioncancelDuplicate(){ // 连续发起相同参数的请求 http.getPageResponseUser('/api/users', { params: { page:1} });// 被取消 http.getPageResponseUser('/api/users', { params: { page:1} });// 被取消 constlatestData =awaithttp.getPageResponseUser('/api/users', { params: { page:1} });// 最终生效 } 5.3 页面卸载时取消所有请求// 在React/Vue等框架的组件卸载钩子中调用 functiononPageUnmount(){ http.cancelAllRequests('页面已关闭'); } // 或者监听页面关闭事件 window.addEventListener('beforeunload',()={ http.cancelAllRequests('用户离开页面'); }); 「带来的好处」: 减少不必要的网络请求和服务器负载避免过时数据覆盖最新数据防止页面跳转后仍弹出错误提示减少内存占用和潜在的内存泄漏6. 文件上传:大文件处理的最佳实践文件上传是前端开发中的常见需求,尤其需要注意超时设置和数据格式。HttpClient 可以轻松配置适合文件上传的参数。 // 定义上传结果接口 interfaceUploadResult { url:string; // 上传后的文件URL filename:string;// 文件名 size:number; // 文件大小 } // 处理文件上传 asyncfunctionuploadFile(file: File){ // 创建FormData对象 constformData =newFormData(); formData.append('file', file); // 可选:添加其他表单字段 formData.append('category','document'); formData.append('description','用户上传的文档'); // 创建上传专用实例(配置更长的超时) constuploadHttp =newHttpClient({ timeout:60000, // 上传超时设为60秒 headers: {'Content-Type':'multipart/form-data'} try{ constresult =awaituploadHttp.postUploadResult('/api/upload', formData); console.log('文件上传成功,访问地址:', result.url); returnresult.url; }catch(error) { console.error('文件上传失败:', error); throwerror; } } 「上传优化建议」: 大文件上传使用专门的实例,设置较长超时配合进度条展示上传进度(可通过 Axios 的 onUploadProgress 实现)考虑分片上传大文件(超过100MB的文件)重要文件上传可配置重试机制7. 并发请求处理:高效获取多源数据实际开发中经常需要同时请求多个接口,然后汇总处理数据。HttpClient 结合 Promise API 可以优雅地处理并发请求。 // 使用Promise.all处理并发请求 asyncfunctionhandleConcurrentRequests(){ try{ // 同时发起多个请求 const[userRes, productRes] =awaitPromise.all([ http.getUser('/api/users/1'), // 获取用户详情 http.getPageResponseProduct('/api/products')// 获取商品列表 // 所有请求成功后处理数据 console.log('用户详情:', userRes); console.log('商品列表:', productRes.list); // 可以在这里进行数据整合 return{ user: userRes, products: productRes.list }catch(error) { // 任何一个请求失败都会进入这里 console.error('并发请求失败:', error); throwerror; } } 「并发处理技巧」: 使用Promise.all处理相互依赖的并发请求(一失败全失败)使用Promise.allSettled处理可以独立失败的请求对大量并发请求进行分批处理,避免浏览器限制结合请求取消机制,在某个关键请求失败时取消其他请求8. 请求重试:提升网络不稳定场景的可靠性网络波动是前端请求失败的常见原因,HttpClient 内置的重试机制可以自动处理这类问题,提升用户体验。 8.1 基础重试配置(使用默认策略)// 使用默认重试条件 asyncfunctionbasicRetryWithDefaults(){ try{ constresult =awaithttp.getUser('/api/users/1', { retry: { retries:3, // 最多重试3次 retryDelay:1000// 每次重试间隔1秒 // 默认策略:只重试网络错误或5xx服务器错误 } returnresult; }catch(error) { console.error('所有重试都失败了:', error); throwerror; } } 8.2 自定义重试条件// 自定义重试逻辑 asyncfunctioncustomRetryCondition(userData: User){ try{ constresult =awaithttp.postUser('/api/users', userData, { retry: { retries:2, // 重试2次 retryDelay:500, // 重试间隔500ms retryCondition:(error) ={ // 自定义条件:网络错误、超时或5xx错误才重试 return!error.response || error.response.status ===408|| (error.response.status =500&& error.response.status 600); } } returnresult; }catch(error) { console.error('重试失败:', error); throwerror; } } 「重试策略建议」: 读操作(GET)适合重试,写操作(POST/PUT)需谨慎重试次数不宜过多(通常2-3次),避免加重服务器负担使用指数退避策略(retryDelay 逐渐增加)对明确的客户端错误(如400、401、403)不重试9. 访问原始 Axios 实例:兼容特殊需求虽然 HttpClient 封装了常用功能,但某些特殊场景可能需要直接使用 Axios 原生 API。HttpClient 提供了获取原始实例的方法。 // 获取原始Axios实例 functiongetOriginalAxiosInstance(){ constaxiosInstance = http.getInstance(); // 示例1:使用Axios的cancelToken(旧版取消方式) constCancelToken = Axios.CancelToken; constsource = CancelToken.source(); axiosInstance.get('/api/special', { cancelToken: source.token // 取消请求 source.cancel('Operation canceled by the user.'); // 示例2:使用Axios的拦截器API constmyInterceptor = axiosInstance.interceptors.response.use( response=response, error=Promise.reject(error) // 移除拦截器 axiosInstance.interceptors.response.eject(myInterceptor); } 「适用场景」: 使用一些 HttpClient 未封装的 Axios 特性集成依赖原始 Axios 实例的第三方库处理极特殊的请求场景平滑迁移现有基于 Axios 的代码?? 插曲:朋友的无情嘲笑在我们完成本次封装前,还有一个小插曲:我得意洋洋地把这个"史上最优雅"的封装发给朋友炫耀,心想着他肯定会夸我两句。结果他发来了一大段文字... 第一轮攻击:类型定义问题「朋友」: "你这代码有点问题啊 ?? 你这个ResponseData类型扩展性不足:" // 你的问题代码 exporttypeResponseDataT =any = BaseResponse // 当 T 中包含 code 或 message 字段时会冲突 interfaceUserWithCode { code:string;// 与BaseResponse冲突 name:string; } typeTestType = ResponseDataUserWithCode// code字段变成never类型! 「我」: "不可能!绝对不可能!(曹操.gif)" 然后我测试了一下,果然报错了... ?? 「解决方案」: // 修复后的版本 typeOmitBaseResponse = OmitT, keyof BaseResponse exporttypeResponseDataT =any = BaseResponse & OmitBaseResponse 第二轮攻击:请求取消逻辑缺陷「朋友」: "还有你这个setupCancelController未处理自定义requestKey冲突,当用户传入自定义requestKey时,若与内部生成的键重复,会导致取消逻辑混乱。" 「我」: "这... 这应该不会吧?" 「朋友」: "你看,你的代码是这样的: privatesetupCancelController(config: AxiosRequestConfig, requestKey?: RequestKey) { constkey = requestKey ||this.getRequestKey(config); // 直接取消,但没有冲突警告 this.cancelRequest(key); } 如果有重复key怎么办?建议加个警告。" 「我」: "但是我这里直接取消重复请求不是挺好的吗?这是防重复机制啊!" 「朋友」: "嗯...这个倒是有道理。那算了,这个问题不大。" 第三轮攻击:重试机制边界问题「朋友」: "但是你这个重试逻辑有问题!重试时未重置AbortController:" // 你的问题代码 for(letattempt =0; attempt = retryConfig.retries; attempt++) { // 每次都会调用setupCancelController,创建新的controller // 但旧的还在Map中,可能导致重试请求被误取消 constrequestConfig =this.setupCancelController({...restConfig, method, url}, requestKey); } 「我」: "这... 这是边缘情况!" 「朋友」: "边缘情况也是情况啊!还有你的retryCondition默认值缺失,当用户未配置时,会默认重试所有错误(包括400等客户端错误),不符合预期。" 「我」: "好吧好吧,我改还不行吗... ??" 「解决方案」: // 修复后的版本 constdefaultRetryCondition =(error: AxiosError) ={ // 默认只重试网络错误或5xx服务器错误 return!error.response || (error.response.status =500&& error.response.status 600); }; for(letattempt =0; attempt = retryConfig.retries; attempt++) { // 重试前清除旧控制器 if(attempt 0&& key) { this.abortControllers.delete(key); } constrequestConfig =this.setupCancelController({...restConfig, method, url}, requestKey); } 第四轮攻击:拦截器管理问题「朋友」: "还有你的拦截器移除逻辑不严谨,只通过interceptorId移除,但未重置interceptorId,可能导致后续重复移除无效:" // 你的问题代码 publicremoveRequestInterceptor():void{ if(this.requestInterceptorId) { this.instance.interceptors.request.eject(this.requestInterceptorId); // 没有重置ID! } } 「我」: "这... 好吧,确实应该重置一下。" 「解决方案」: // 修复后的版本 publicremoveRequestInterceptor():void{ if(this.requestInterceptorId !==undefined) { this.instance.interceptors.request.eject(this.requestInterceptorId); this.requestInterceptorId =undefined;// 重置ID } } 第五轮攻击:其他细节问题「朋友」: "哈哈,别急。不过你还有几个问题: 「Content-Type硬编码问题」- 默认强制设置为application/json,但上传文件时需要multipart/form-data,需手动覆盖,不够灵活。 「错误信息处理冗余」- 响应错误拦截器中对错误信息的包装会丢失原始错误的详细信息,不利于调试。 「requestKey类型声明不明确」- 定义为string | symbol,但用户传入symbol时,调试信息显示不友好。" 「我」: "停停停!你这是在 code review 还是在找茬?!" 「朋友」: "当然是 code review 啦,不过后面这几个确实比较鸡蛋里挑骨头,前面几个确实需要修复。" 我的反击与分析经过一番"友好"的讨论(主要是我被教育),我冷静分析了一下: 「确实有价值的问题(必须修复):」 ?「ResponseData 类型冲突」- 很重要!确实会导致never类型问题?「重试机制的默认条件缺失」- 重要!应该有合理的默认重试条件?「拦截器ID重置问题」- 中等重要,确实应该重置ID?「重试时AbortController重置」- 中等重要,理论上存在问题「过于苛刻或设计选择问题:」 ?「requestKey冲突警告」- 当前设计已经通过取消旧请求处理了,警告是多余的?「Content-Type硬编码」- 这是常见的默认设置,Axios会自动覆盖FormData?「错误信息包装」- 保留原始错误是好的,但当前设计也合理?「拦截器组合模式」- 当前的覆盖模式是主流设计,组合模式会增加复杂性「主观性问题:」 ??「requestKey类型限制」- symbol支持是特性,不是缺陷最终我不得不承认:「朋友的技术功底是不错的,提出了一些确实存在的边缘问题。但有些建议过于"完美主义",可能会让代码变得过于复杂。」 修复了前4个重要问题后,朋友终于点头说:"现在看起来像个正经的封装了!不过你得承认,好的代码不仅要能跑,还要经得起同行的审视。" ?? ?? 写在最后经过这一上午的"激情"编码 + 朋友的"无情"嘲笑 + 我的"不服气"修复,这个 HTTP 客户端封装终于变得更加健壮了。 从最初的自我感觉良好,到被朋友无情打脸,再到最后的虚心修复,这个过程让我深刻体会到: 「没有完美的代码」- 总有你想不到的边缘情况「Code Review 很重要」- 别人的视角能发现你的盲点「保持开放心态」- 被指出问题是好事,不是坏事「朋友很重要」- 能"嘲笑"你代码的朋友才是真朋友 ??现在这个封装不仅解决了日常开发中的痛点,还具备了企业级项目的稳定性。 「核心优势:」 ??「开箱即用」- 无需复杂配置,默认就很好用??「高度可定制」- 支持各种业务场景的定制需求???「类型安全」- TypeScript 完美支持,减少运行时错误?「性能出色」- 智能去重、重试、取消机制??「文档完善」- 详细的示例和注释如果你也在为 HTTP 请求封装而苦恼,不妨试试这个方案。记住,「好的代码是改出来的,不是写出来的」! 「你有过类似被朋友"嘲笑"代码的经历吗?你觉得这个封装怎么样?欢迎在评论区分享你的故事!」?? AI编程资讯AI Coding专区指南: https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2025-09-07_《凡人修仙传》暑期热播,虚拟飞行系统引领拍摄新纪元 下一篇:2025-08-14_「转」瑞幸新联名,给广东人整笑了!

TAG标签:

19
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价