Axios拦截器实战:从请求到响应的全流程控制

张开发
2026/5/3 9:38:43 15 分钟阅读
Axios拦截器实战:从请求到响应的全流程控制
1. Axios拦截器入门前端开发的瑞士军刀第一次接触Axios拦截器是在三年前的一个电商项目里当时需要给所有请求自动添加Token。当我发现每个API调用都要手动设置header时整个人都不好了。直到同事扔给我一段拦截器代码才真正体会到什么叫一行代码解决所有问题。Axios拦截器本质上是个中间件管道就像快递公司的分拣系统。请求发出前要经过请求拦截器request interceptor处理收到响应后要经过响应拦截器response interceptor加工。这个机制让统一处理认证、日志、错误等需求变得异常简单。来看个最简单的例子// 添加请求拦截器 axios.interceptors.request.use(config { config.headers.Authorization Bearer token123 return config }) // 添加响应拦截器 axios.interceptors.response.use(response { console.log(收到来自${response.config.url}的响应) return response })这段代码实现了两个最常用功能自动给所有请求添加认证头记录所有响应日志实际项目中拦截器能做的事情远不止这些。比如我们可以在请求阶段统一添加时间戳参数压缩请求数据显示全局loading动画在响应阶段可以统一处理错误码解压响应数据隐藏loading动画2. 拦截器核心机制详解2.1 洋葱模型与执行顺序很多初学者容易误解拦截器的执行顺序。假设我们注册了多个拦截器// 请求拦截器A axios.interceptors.request.use(config { console.log(请求拦截器A) return config }) // 请求拦截器B axios.interceptors.request.use(config { console.log(请求拦截器B) return config }) // 响应拦截器X axios.interceptors.response.use(response { console.log(响应拦截器X) return response }) // 响应拦截器Y axios.interceptors.response.use(response { console.log(响应拦截器Y) return response })实际执行顺序是B → A → 服务器 → X → Y。这个反直觉的顺序源于Axios内部的unshift操作。源码中请求拦截器是通过unshift方法添加到调用链前端的所以后注册的反而先执行。2.2 错误处理的最佳实践拦截器的错误处理经常被忽视。来看个典型的错误示范axios.interceptors.response.use(response { if (response.status ! 200) { throw new Error(请求失败) } return response })这种写法会导致业务代码的catch无法捕获错误。正确的做法应该是axios.interceptors.response.use( response response, error { if (error.response.status 401) { router.push(/login) } return Promise.reject(error) // 关键让错误继续传递 } )在业务代码中就可以正常捕获了axios.get(/api).catch(err { console.log(这里能捕获到拦截器reject的错误) })3. 企业级实战案例3.1 自动化Token刷新方案在需要长期维持登录状态的系统中Token过期处理是个经典难题。通过拦截器可以优雅地解决let isRefreshing false let requestsQueue [] axios.interceptors.response.use(null, async error { if (error.config error.response?.status 401) { if (!isRefreshing) { isRefreshing true try { const newToken await refreshToken() localStorage.setItem(token, newToken) requestsQueue.forEach(cb cb(newToken)) return axios(error.config) } finally { isRefreshing false requestsQueue [] } } return new Promise(resolve { requestsQueue.push(token { error.config.headers.Authorization Bearer ${token} resolve(axios(error.config)) }) }) } return Promise.reject(error) })这个方案实现了检测到401错误自动刷新Token避免并发刷新排队等待中的请求在刷新后自动重试3.2 性能监控与埋点拦截器也是实现监控的绝佳位置const metrics { requestCount: 0, successCount: 0, errorCount: 0, totalTime: 0 } axios.interceptors.request.use(config { config.metadata { startTime: Date.now() } metrics.requestCount return config }) axios.interceptors.response.use( response { const duration Date.now() - response.config.metadata.startTime metrics.totalTime duration metrics.successCount logToServer(api-success, { url: response.config.url, duration }) return response }, error { metrics.errorCount logToServer(api-error, { url: error.config.url, status: error.response?.status }) return Promise.reject(error) } )4. 高级技巧与原理剖析4.1 动态禁用拦截器有时候我们需要临时跳过某些拦截器。Axios提供了eject方法const requestId axios.interceptors.request.use(config { config.headers[X-Debug] true return config }) // 需要跳过时 axios.interceptors.request.eject(requestId)更优雅的做法是通过自定义配置axios.interceptors.request.use(config { if (config.skipInterceptor) return config // 正常处理逻辑 return config }) // 使用方式 axios.get(/api, { skipInterceptor: true })4.2 拦截器与CancelToken的结合在文件上传等场景中可能需要取消请求const source axios.CancelToken.source() axios.interceptors.request.use(config { if (config.cancelToken) { config.onCancel () { source.cancel(请求被拦截器取消) } } return config }) // 取消请求 source.cancel()4.3 源码级执行流程Axios的拦截器实现非常精妙核心在于Promise链的构建初始化chain数组包含默认的dispatchRequest通过unshift添加所有请求拦截器通过push添加所有响应拦截器使用while循环构建Promise链// 简化版实现 let chain [dispatchRequest, null] let promise Promise.resolve(config) // 添加请求拦截器 this.interceptors.request.forEach(interceptor { chain.unshift(interceptor.fulfilled, interceptor.rejected) }) // 添加响应拦截器 this.interceptors.response.forEach(interceptor { chain.push(interceptor.fulfilled, interceptor.rejected) }) while(chain.length) { promise promise.then(chain.shift(), chain.shift()) }这种实现方式保证了请求拦截器先进后出FILO响应拦截器先进先出FIFO统一的错误处理机制5. 常见问题排查指南5.1 拦截器导致无限循环曾经在项目中遇到过刷新Token的死循环问题axios.interceptors.response.use(null, async error { if (error.response.status 401) { const newToken await refreshToken() // 这里用的也是axios // ... } })解决方案是创建独立的axios实例用于Token刷新const authHttp axios.create() axios.interceptors.response.use(null, async error { if (error.response.status 401) { const newToken await authHttp.post(/refresh) // ... } })5.2 响应数据被多次处理有时候会发现响应数据被多次修改这通常是因为重复注册了相同的拦截器拦截器没有正确返回处理后的数据正确的做法应该是axios.interceptors.response.use(response { // 修改响应数据 response.data transform(response.data) // 必须返回整个response对象 return response })5.3 TypeScript类型增强在TS项目中可以通过声明合并来扩展Axios类型declare module axios { interface AxiosRequestConfig { skipInterceptor?: boolean metadata?: { startTime?: number } } }这样在使用自定义配置时就能获得类型提示了。

更多文章