业务需求
公司有多个项目共同用到了已封装好的axios(funcation http()),基于去掉复制粘贴这种重复工作。
- 统一封装所有项目axios方法()
- 把封装好的方法打包发布到公司npm私服(不涉及)
问题重现
Vue项目页面Create(){}的时候调用了一个接口,相当于调用了http()方法,此时响应response还是正常的。
但是当再调用任何接口的时候,response出现了多次(我在响应拦截器中console了response,调用了几次http(),就多出现几次console)
// axios 封装的方法(关键代码)
// httpClient是axios重命名、因为每个项目的baseURL不一样,所以每次需要传一个base进来、url是接口地址
export default function http(base, url, data = {}, type = "get", header = {}) {
httpClient.defaults.baseURL = base;
httpClient.interceptors.response.use(
response => {
console.log("response", response);
return response
},
error => {
console.log("error", error.response);
if (error.response.status === 401 && error.response.data.code === 'nosso') {
window.location.href = base + '/redirect/to/frontend?page=' + encodeURIComponent(window.location.href)
}
return error.response
}
);
type = type.toLowerCase();
let promise;
return new Promise((resolve, reject) => {
if (type === 'post') {
promise = httpClient.post(url, data, {headers: header})
} else if (type === 'put') {
promise = httpClient.put(url, data, {headers: header})
} else if (type === 'delete') {
promise = httpClient.delete(url, {data: data, headers: header})
} else {
promise = httpClient.get(url, {params: data, headers: header})
}
promise.then(response => {
resolve(response.data);
}).catch(error => {
reject(error)
})
})
}
如果再点退出,response的数量会继续累加。当时看到这个情况后很不理解。首先能确定的是两个请求格式和参数完全一致,那么问题只能在响应和后端数据上了(排除后台数据问题)。
百度了一圈,基本没有发现这个问题的解决办法(本来是没有问题的,这个封装的方法最后是由我自己改动的。详细改动:将baseURL和响应拦截器放在了http()里面,就出现以上错误。之所以会放在里面是因为base位置问题,后面细说)。
为什么会出现这个问题 && 问题原因
项目以前http()的响应拦截器里的地址是写死的,封装后,需要根据不同的项目给不同的请求前缀地址。而axios默认的请求配置是在发生请求之前定义的。
如图,调用http()的时候,需要将一个base传入baseURL和响应拦截器里面。因为是配置项,有点违背代码逻辑,所以将baseURL和响应拦截器放在http()里面,如下图:
回到上面的问题,我猜测把响应拦截器放在http()后,因为每次都有个new Promise的过程,相当于在项目里,每调一次http(),axios就多一个响应拦截器配置,每个响应都依次依赖,所以才导致调用一个接口,就多一个response结果。
解决
找到问题就好解决,最开始没有想到,httpClient.default.baseURL可以当做axios服务的全局变量。也就是说,我只需要把这个default.baseURL放在http()里面,响应拦截器放在外面就可以了
httpClient.interceptors.response.use(
response => {
return response.data
},
error => {
if (error.response.status === 401 && error.response.data.code === 'nosso') {
window.location.href = httpClient.defaults.baseURL + '/redirect/to/frontend?page=' + encodeURIComponent(window.location.href)
}
return error.response
}
);
/**
* 封装请求方法
* @params(url)
* @params(data)
* returns { Promise }
* @user NIGangJun <nigangjun@aeotrade.com>
*/
export default function http(base, url, data = {}, type = "get", header = {}) {
httpClient.defaults.baseURL = base;
type = type.toLowerCase();
let promise;
return new Promise((resolve, reject) => {
if (type === 'post') {
promise = httpClient.post(url, data, {headers: header})
} else if (type === 'put') {
promise = httpClient.put(url, data, {headers: header})
} else if (type === 'delete') {
promise = httpClient.delete(url, {data: data, headers: header})
} else {
promise = httpClient.get(url, {params: data, headers: header})
}
promise.then(response => {
resolve(response);
})
})
}
就是把响应拦截器放在外面,然后base替换成baseURL就行了。
有个优化的地方:响应拦截器resolve的数据换成了response.data,reject的数据换成了error.response。promise就直接去掉了catch(如果要catch,响应拦截器中reject的数据要return Promise.reject(error.response),并且http()方法里不能再使用 new Promise封装)
总结
虽然最后解决结果很简单,但是中间这个过程不可忽略。
- axios配置应该符合规范;
- 查找代码问题应该按照功能进行拆分,依次查询;