Axios 的实战应用:从基础请求到高级拦截器配置
1. 初识Axios前端开发的HTTP利器第一次接触Axios是在三年前的一个电商项目里。当时团队正在为频繁出现的跨域请求和回调地狱头疼不已直到有人推荐了这个基于Promise的HTTP库。说实话刚开始我还有点怀疑——毕竟jQuery的$.ajax已经用了好几年但真正上手后才发现Axios确实香。Axios本质上是一个轻量级的HTTP客户端它最大的特点是同时支持浏览器和Node.js环境。这意味着你可以在前端项目和后端服务中使用相同的API发起请求这种一致性对全栈开发者特别友好。我记得当时最让我惊喜的是它自动转换JSON数据的能力再也不用手动调用JSON.parse()了。与原生fetch API相比Axios提供了更人性化的错误处理机制。当服务器返回4xx或5xx状态码时fetch不会自动reject而Axios会直接进入catch块这对快速定位问题特别有帮助。下面是个最简单的GET请求示例axios.get(/api/products) .then(response { console.log(response.data); // 自动解析的JSON数据 }) .catch(error { console.error(出错了:, error); // 自动捕获HTTP错误 });在实际项目中我特别喜欢Axios的取消请求功能。比如用户快速切换商品分类时可以取消前一个未完成的请求避免数据错乱。这个功能在实现搜索建议框时特别实用const source axios.CancelToken.source(); axios.get(/api/search, { params: { q: 手机 }, cancelToken: source.token }).catch(err { if (axios.isCancel(err)) { console.log(请求被取消:, err.message); } else { // 处理其他错误 } }); // 需要取消请求时 source.cancel(用户取消操作);2. 基础请求实战GET/POST的进阶技巧2.1 GET请求的参数处理新手最容易踩的坑就是GET请求传参。我见过不少开发者直接把参数拼在URL后面这样既难看又容易出错。Axios提供了两种更优雅的方式第一种是通过params对象Axios会自动将其转换为查询字符串axios.get(/api/products, { params: { category: electronics, sort: price, page: 2 } }); // 实际请求URL: /api/products?categoryelectronicssortpricepage2第二种是使用URLSearchParams对象特别适合需要数组参数的情况const params new URLSearchParams(); params.append(color, red); params.append(size, [S, M]); // 自动处理数组 axios.get(/api/products, { params });2.2 POST请求的数据格式POST请求的数据处理更有讲究。默认情况下Axios会自动将JavaScript对象序列化为JSON但有时候我们需要发送FormData或URL编码数据。这里分享几个实战技巧JSON格式默认axios.post(/api/checkout, { items: [ { id: 1, quantity: 2 }, { id: 3, quantity: 1 } ], address: 北京市朝阳区 });FormData格式适合文件上传const formData new FormData(); formData.append(avatar, fileInput.files[0]); formData.append(username, 张三); axios.post(/api/upload, formData, { headers: { Content-Type: multipart/form-data } });URL编码格式const data new URLSearchParams(); data.append(username, admin); data.append(password, 123456); axios.post(/api/login, data, { headers: { Content-Type: application/x-www-form-urlencoded } });2.3 并发请求处理在商品详情页我们经常需要同时获取商品信息、库存状态和用户评价。使用axios.all可以优雅地处理这种场景const getProduct axios.get(/api/product/123); const getInventory axios.get(/api/inventory/123); const getReviews axios.get(/api/reviews?product_id123); axios.all([getProduct, getInventory, getReviews]) .then(axios.spread((product, inventory, reviews) { // 三个请求都完成后执行 console.log(商品详情:, product.data); console.log(库存状态:, inventory.data); console.log(用户评价:, reviews.data); })) .catch(error { console.error(获取数据失败:, error); });3. 拦截器打造企业级请求处理管道3.1 请求拦截器的妙用在真实项目中拦截器(Interceptor)绝对是Axios最强大的功能。去年我们重构用户系统时就用它统一处理了JWT认证。请求拦截器可以在请求发出前进行修改最常见的场景就是添加认证头axios.interceptors.request.use(config { // 从localStorage获取token const token localStorage.getItem(auth_token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }, error { return Promise.reject(error); });另一个实用技巧是自动添加时间戳防止缓存axios.interceptors.request.use(config { if (config.method get) { config.params { ...config.params, _t: Date.now() }; } return config; });3.2 响应拦截器的错误处理响应拦截器可以统一处理服务器响应。我们项目中的标准做法是将业务错误和HTTP错误分开处理axios.interceptors.response.use( response { // 2xx范围内的状态码都会触发该函数 if (response.data.code ! 0) { // 业务逻辑错误 const error new Error(response.data.message); error.code response.data.code; return Promise.reject(error); } return response.data; // 直接返回data字段 }, error { // 超出2xx范围的状态码都会触发该函数 if (error.response) { switch (error.response.status) { case 401: // 跳转到登录页 window.location.href /login; break; case 403: // 显示权限不足提示 alert(您没有权限执行此操作); break; case 500: // 跳转到错误页 window.location.href /error; break; } } return Promise.reject(error); } );3.3 加载状态管理在Vue/React项目中我们经常需要显示全局加载状态。通过拦截器可以优雅地实现let loadingCount 0; const loading { show: () console.log(显示加载动画), hide: () console.log(隐藏加载动画) }; axios.interceptors.request.use(config { loadingCount; if (loadingCount 1) loading.show(); return config; }); axios.interceptors.response.use( response { loadingCount--; if (loadingCount 0) loading.hide(); return response; }, error { loadingCount--; if (loadingCount 0) loading.hide(); return Promise.reject(error); } );4. 高级配置自定义实例与项目实战4.1 创建自定义实例大型项目中我们通常需要针对不同API创建多个Axios实例。比如电商项目可能有商品服务、用户服务和支付服务三个独立后端// 商品服务实例 const productAPI axios.create({ baseURL: https://api.example.com/products, timeout: 5000 }); // 用户服务实例 const userAPI axios.create({ baseURL: https://api.example.com/users, timeout: 3000 }); // 支付服务实例 const paymentAPI axios.create({ baseURL: https://pay.example.com, timeout: 10000 });4.2 实战配置技巧超时重试机制axios.interceptors.response.use(undefined, err { const config err.config; if (!config || !config.retry) return Promise.reject(err); config.__retryCount config.__retryCount || 0; if (config.__retryCount config.retry) { return Promise.reject(err); } config.__retryCount 1; return new Promise(resolve { setTimeout(() resolve(axios(config)), config.retryDelay || 1000); }); }); // 使用示例 axios.get(/api/data, { retry: 3, retryDelay: 1000 });文件下载处理axios.get(/api/export, { responseType: blob }).then(response { const url window.URL.createObjectURL(new Blob([response.data])); const link document.createElement(a); link.href url; link.setAttribute(download, report.xlsx); document.body.appendChild(link); link.click(); document.body.removeChild(link); });4.3 安全防护配置CSRF防护const csrfToken document.querySelector(meta[namecsrf-token]).content; axios.defaults.headers.common[X-CSRF-TOKEN] csrfToken;请求速率限制const rateLimitMap new Map(); axios.interceptors.request.use(config { const url config.url; const now Date.now(); if (rateLimitMap.has(url)) { const lastRequestTime rateLimitMap.get(url); if (now - lastRequestTime 1000) { // 1秒内只能请求一次 return Promise.reject(new Error(请求过于频繁)); } } rateLimitMap.set(url, now); return config; });5. 性能优化与调试技巧5.1 请求缓存策略对于不常变动的数据可以实现简单的内存缓存const cache new Map(); axios.interceptors.request.use(config { if (config.method get config.cache) { const cacheKey JSON.stringify(config); if (cache.has(cacheKey)) { return Promise.resolve(cache.get(cacheKey)); } } return config; }); axios.interceptors.response.use(response { if (response.config.method get response.config.cache) { const cacheKey JSON.stringify(response.config); cache.set(cacheKey, response); } return response; }); // 使用示例 axios.get(/api/categories, { cache: true });5.2 调试与日志记录开发环境下可以添加请求日志拦截器axios.interceptors.request.use(config { console.log([Request] ${config.method.toUpperCase()} ${config.url}, { params: config.params, data: config.data }); return config; }); axios.interceptors.response.use(response { console.log([Response] ${response.status} ${response.config.url}, response.data); return response; }, error { if (error.response) { console.error([Error] ${error.response.status} ${error.config.url}, error.response.data); } else { console.error([Error], error.message); } return Promise.reject(error); });5.3 取消重复请求在实现搜索建议时取消之前的未完成请求非常重要const pendingRequests new Map(); axios.interceptors.request.use(config { const requestKey ${config.method}-${config.url}-${JSON.stringify(config.params)}; if (pendingRequests.has(requestKey)) { pendingRequests.get(requestKey).cancel(取消重复请求); } const cancelToken new axios.CancelToken(cancel { pendingRequests.set(requestKey, { cancel }); }); config.cancelToken cancelToken; return config; }); axios.interceptors.response.use(response { const requestKey ${response.config.method}-${response.config.url}-${JSON.stringify(response.config.params)}; pendingRequests.delete(requestKey); return response; }, error { if (!axios.isCancel(error)) { const requestKey ${error.config.method}-${error.config.url}-${JSON.stringify(error.config.params)}; pendingRequests.delete(requestKey); } return Promise.reject(error); });