从一段有问题的axios,学习axios封装以及axios知识点,大家一起来找问题
1.问题代码
1.1问题描述
使用封装Ajax调用接口报错之后,并未进入error,去执行清空定时器的操作,导致接口报错之后,定时器未关闭,一直调用报错接口
1.2使用Ajax组件
import Axios from 'axios';
//响应拦截器
Axios.interceptors.response.use(
response => { return response; },
error => {
console.error('拦截器:',error);
try {
console.error('拦截器Try:',error);
if (error.response.status === 401) {
window.location.href = '/login';
}
} catch(e) {
console.error('拦截器catch:',e);
}
console.log('拦截器执行了Promise.reject(error)')
return Promise.reject(error);
}
)
const errorStatusHandle = function (data, headers,statusTemp) {
console.log('errorStatusHandle参数打印:', data, headers,statusTemp)
if (statusTemp === 500) {
return null
}
if(statusTemp === 401) {
return null
}
return data
}
const AjaxMain = (params1, error = ()=> {}, success = ()=> {}) => {
let {
url,
params,
methods
} = params1
Axios({
url,
params,
methods,
validateStatus: (status)=> {
return status < 500
},
transformResponse:[
function(data,headers,status) {
if (!errorStatusHandle(data, headers,status)) {
console.log('transformResponse参数打印:',data, headers,status)
throw new Error(data)
}
}
]
}).then(res => {
console.log('AjaxMain.then打印:', res)
if (res.status === 200) {
let successStatus= true
if (successStatus) {
success(res)
} else {
error(data)
}
}
})
.catch(err => {
console.log('AjaxMain.catch打印:', err)
if (!errorStatusHandle(err, url)) {
return null
}
})
}
export default AjaxMain
1.3封装Ajax代码
import React, { Component } from 'react'
import Ajax from './ajax'
import axios from 'axios'
export default class TopSearch extends Component {
serchBtn = () => {
console.log('进入搜索', this.keydownVal.value)
let searchVal = this.keydownVal.value
// 使用代理请求数据--5000代理器
Ajax({
url: 'https://api.github.com/search/users',
params: {
q:searchVal
},
methods: 'get'
},
res => {console.log(res, '成功')
},
error => {
console.log(error, '报错信息')
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">Search Github Users</h3>
<div>
<input ref={c => this.keydownVal= c} type="text" placeholder="enter the name you search"/>
<button onClick={this.serchBtn}>搜索</button>
</div>
</section>
)
}
}
2. 解决思路
2.1 解决问题思路
1.首先封装使用的是回调暴露错误的-检查代码封装是否在所有报错会进入的地方都执行了error()-检查发现AjaxMain.catch中没有执行error() 2.检查是否有抛出错误被吞掉的问题,以及错误抛出错误会导致走向哪里 3.对于不同的报错码,是否会进入catch还是then
3. Axios知识点总结
3.1 Axios封装的俩种主流方式
- 回调函数方式(callback)
在请求方法中传直接传入success和error俩个回调函数,内部根据请求结果调用对应的回调 - 返回promise的方式
封装方法直接返回Axios调用产生的promise对象,调用是使用.then,.catch或async/await获取结果3.1.1 回调函数方式(callback)
1)特点:- 显示控制成功和失败的路径:调用方需要提供一个成功函数和一个失败函数
- 适合简单的场景:如早起的jQuery的$.ajax风格。
- 代码嵌套有风险:如果多个请求有依赖关系,容易形成"回调地狱" 2)代码案例:
import axios from 'axios'; /** * 通用请求封装(回调版) * @param {Object} config - axios 配置对象 * @param {Function} success - 请求成功时的回调,接收响应数据 * @param {Function} error - 请求失败时的回调,接收错误对象 */ function request(config, success, error) { axios(config) .then(response => { // 成功:调用 success 回调,通常传入 response.data success(response.data); }) .catch(err => { // 失败:调用 error 回调,可以传入错误对象或自定义信息 error(err); }); } // 使用示例:获取用户信息 request( { url: '/api/user', method: 'get' }, (data) => { console.log('用户数据:', data); // 更新 UI 等操作 }, (err) => { console.error('获取用户失败:', err.message); // 显示错误提示 } );3.1.2 返回promise的方式
1)特点:- 返回 Promise 对象:封装函数直接返回 axios(config) 的结果,该结果本身就是一个 Promise。
- 调用灵活:可以使用 .then().catch() 或 async/await,支持链式调用。
- 易于组合:Promise.all、Promise.race 等并发控制非常方便。
- 主流实践:目前大多数现代前端项目都采用这种方式。 2)代码案例:
import axios from 'axios'; /** * 通用请求封装(Promise 版) * @param {Object} config - axios 配置对象 * @returns {Promise} 返回 axios 请求的 Promise */ function request(config) { return axios(config); } // 使用示例:获取用户信息 request({ url: '/api/user', method: 'get' }) .then(response => { console.log('用户数据:', response.data); }) .catch(error => { console.error('请求失败:', error.message); }); // 或者使用 async/await async function fetchUser() { try { const response = await request({ url: '/api/user' }); console.log('用户数据:', response.data); } catch (error) { console.error('请求失败:', error.message); } }
3.2 关于响应拦截器和transformResponse使用区别
3.2.1 transformResponse:纯粹的数据转换器
-
核心功能: transformResponse 是一个纯粹的数据转换函数,专用于同步地修改响应体(data)和响应头(headers)。例如,其常见用途是解析后端返回的 JSON 字符串,或处理一些特殊数据格式,比如使用 json-bigint 库来解析可能超出 JavaScript 安全整数范围的大数字
-
主要特点:
同步操作: 设计上必须是同步的,以保证数据处理的顺序性。
只关注数据: 其函数签名通常为 (data, headers) => transformedData,只能访问 data 和 headers,无法操作完整的响应对象或请求配置。但是也可以获取status请求状态
内置于请求链: 它是 Axios 请求分发(dispatchRequest)流程中的一环,位于请求拦截器之后、响应拦截器之前,是所有请求都必经的一个“内置关卡”。 -
使用场景
全局数据格式统一: 例如,所有接口返回的日期字符串都需要转换成 Date 对象。
数据反序列化: 后端返回的是特殊格式的字符串(如 XML、自定义格式),需要在业务逻辑处理前统一转换。
轻量级同步处理: 只是需要简单、快速地修改一下响应数据,不需要访问复杂的请求配置或状态码。3.2.2 响应拦截器:强大的流程控制器
-
核心功能: 响应拦截器是一个更通用的流程控制工具。它不仅能处理成功响应,还能捕获错误(即 Promise 链中的 reject 状态),允许进行同步或异步的操作,并执行带有“副作用”的任务。
-
主要特点:
能力全面: 拦截器的函数签名通常为 (response) => response,能访问完整的 response 对象,包括 config(请求配置)、status(HTTP状态码)、data(响应体)和 headers(响应头)等所有信息。
全局与局部: 拦截器通常是全局的,一旦在应用启动时设置,就会作用于通过该 Axios 实例发出的每一个请求。
流程枢纽: 它处于 Promise 链中,位于 transformResponse 处理完成之后,业务代码的 .then 或 .catch 之前,是一个处理HTTP请求生命周期的“中间枢纽”。 -
使用场景 集中错误处理: 根据不同的 HTTP 状态码(如 401 未授权、403 无权限、500 服务端错误)执行统一的业务逻辑,如跳转到登录页或弹出错误提示。
执行异步任务: 需要在拿到响应后,先执行一个异步操作(如刷新 Token),再继续传递数据。
执行有副作用的操作: 例如,在请求完成后,不论成功或失败,都进行统一的埋点日志记录。
需要对完整响应做决策: 当你的处理逻辑依赖于状态码、响应头等信息时(如根据 Content-Disposition 判断文件流),就必须使用拦截器
执行时机与协同工作 两者并非二选一,而是按严格的顺序协同工作: 请求拦截器 -> 2. transformRequest -> 3. 发送请求 -> 4. transformResponse -> 5. 响应拦截器 -> 6. 业务代码 因此,transformResponse 会先于响应拦截器执行。这意味着在响应拦截器中拿到的 response.data,已经是经过 transformResponse 处理后的结果了,如果服务器返回的响应格式异常(例如本应是 JSON 却返回了空字符串、HTML 等),默认的 JSON.parse 操作就会失败并抛出异常或者代码体有异常抛出,在响应拦截器的错误回调中也可以捕获
3.3 关于validateStatus方法
validateStatus 是 Axios 的一个配置项,用于自定义判断 HTTP 响应状态码是否被视为成功(即 Promise 进入 resolve 还是 reject)。
执行时机与影响
执行顺序:响应到达 → transformResponse → validateStatus → 决定 Promise 状态。
如果 validateStatus 返回 false:
Promise 进入 catch 分支。
错误对象 error.response 依然存在,包含完整的响应数据(已经过 transformResponse 转换)。
error.request 和 error.config 也都能访问。
如果请求根本没有到达服务器(如网络故障、DNS 解析失败),不会调用 validateStatus,直接进入 catch,且 error.response 为 undefined。
Axios 的处理流程大致如下:
接收到 HTTP 响应(包括 502)。
调用 transformResponse 对原始响应数据进行转换(无论状态码是否成功)。
根据 validateStatus 判断是否将 Promise 标记为 resolve 或 reject。
如果 validateStatus 返回 false,则进入 catch 分支,但 error.response.data 已经是经过 transformResponse 处理后的数据。
3.4 关于哪些操作会吞掉报错信息,导致无法进入错误回调
具体操作包括以下几种:
-
在拦截器的错误处理函数中返回一个普通值(或 Promise.resolve)
axios.interceptors.response.use( null, error => { if (error.response?.status === 401) { // 返回一个普通对象,表示“我已处理这个错误,请当作成功” return { code: 'AUTH_FAIL', message: '需要登录' }; // 或者 return Promise.resolve({ ... }); } return Promise.reject(error); } );
此时,原本的 401 错误被转换成一个 fulfilled 的 Promise,后续的 .then 会收到这个对象,而 .catch 或回调中的 error 函数不会触发。
- 没有显式返回值(即 return undefined)
axios.interceptors.response.use(
null,
error => {
if (error.response?.status === 401) {
// 做了一些事,但没有 return
console.log('401 发生');
// 相当于 return undefined;
}
return Promise.reject(error);
}
);
当进入 if 分支后,函数执行完毕,隐式返回 undefined。undefined 不是错误,Axios 会将其包装为 Promise.resolve(undefined),从而吞掉错误。
-
在拦截器中抛出错误但没有用 Promise.reject 包装? 实际上,在 async 函数中 throw error 等价于返回 Promise.reject(error),这不会吞掉错误。但如果在非 async 函数中直接 throw error,Axios 会捕获并转为 reject,同样不会吞掉。真正的吞掉是返回非 reject 的值,所以这一点要区分清楚。
-
返回一个最终 resolve 的 Promise(例如刷新 token 后重试成功)
axios.interceptors.response.use(
null,
async error => {
if (error.response?.status === 401) {
await refreshToken();
// 重新发起原请求,假设成功
const newResponse = await axios.request(error.config);
return newResponse; // 返回成功结果 → 吞掉原始 401
}
return Promise.reject(error);
}
);
原始 401 错误被“消化”,最终返回了一个成功的响应数据,错误链不会触发。
- 在拦截器中使用了 try/catch 但没有重新 throw
axios.interceptors.response.use(
null,
error => {
try {
if (error.response?.status === 401) {
// 可能调用某个会抛异常的函数
handle401();
}
return Promise.reject(error);
} catch (e) {
// 捕获了异常,但没有 throw 或 return Promise.reject(e)
console.log('内部错误被吞了', e);
// 隐式返回 undefined → 吞掉
}
}
);`
- 在成功拦截器中(第一个参数)意外返回了非预期值
虽然通常错误由第二个参数处理,但如果在成功拦截器中抛错或返回 Promise.reject,也会进入错误链。反过来,如果在成功拦截器里吞掉错误(比如返回一个普通值),那只是修改了成功数据,不影响错误传递。但要注意:如果成功拦截器里发生了同步错误且没有处理,Axios 会将其转为 reject,这不会吞掉错误。