背景
之前需要写一个api上报的功能,主要是思路是使用axios拦截器中上报API的相关信息。那么我们就来分析下整体方案的实现。
方案
流程图
首先我们先看一下整体设计的流程图:
主要代码示例
根据上面的流程图我们来看下代码的实现:
javascript
export default API = (axiosInstance) => {
const TimerMaxDuration = 6000; // 自定义超时时间
// axiosInstance 是传进来的axios实例
axiosInstance.request.use((config) => {
// ...
config.__apiId = id; // 添加唯一标识 id是根据某种规则生成
const msg = {
id,
begin: Date.now(),
timer: null
};
msg.timer = setTimeout(() => {
// 上报逻辑
...
// 清除相应配置
if (msg.timer) clearTimeout(msg.timer);
this._requests.delete(id);
}, TimerMaxDuration);
this._requests.set(id, msg);
return config;
}, (err) => {
// ...
return Promise.reject(err);
});
axiosInstance.response.use((res) => {
// ...
const id = res.config.__apiId || '';
const msg = this._requests.get(id);
if(msg) {
// 上报逻辑
...
// 清除相应配置
if (msg.timer) clearTimeout(msg.timer);
this._requests.delete(id);
}
return res;
}, (err) => {
// ...
return Promise.reject(err);
});
}
代码解析
- __apiId是根据自定义规则生成唯一标识;
- TimerMaxDuration 是自定义超时时间,使用setTimeout处理超时逻辑;
- 没有超时的情况就在response拦截器中上报处理;
- 上报完成后清除相关配置,避免上报多次。
应用
现在我们已经可以对api上报处理了,就可以应用到我们的项目中。
arduino
// request.js
import axios from 'axios';
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api的base_url
timeout: 50000, // 请求超时时间
});
new API(repoter,service);
至此,我们已经实现整个方案。
完结,撒花~
当我准备开心的下班的时候,同事小强说:我们这里了上报的数据怎么有问题呢,在数据平台上怎么没有看到我们的系统的数据...
那么我们继续往下看看都是遇到了啥问题~
可能遇到问题
当存在多个拦截器
在业务中,很多情况会用axios拦截器来处理统一的业务逻辑,或者对返回的数据进行统一处理等等。那么多个拦截器的情况axios是怎么处理的,我们的api拦截器和业务拦截器之间又有什么影响呢?
接下来主要来分析下axios是如何实现拦截器的,打开axios的源码,找到axios/lib/core/Axios.js文件。
javascript
// axios/lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios的实例化对象,里面有interceptors对象,里面有request和response属性,都是InterceptorManager实例化的对象,我们接着找到axios/lib/core/InterceptorManager.js文件.
javascript
// axios/lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
// 每个use都是维护一个handlers数据,每一项都包含fulfilled,rejected
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 拦截器的forEach方法,遍历handlers每一项
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
从上面可以看到,use是定义在构造函数InterceptorManager的原型上的,我们定义的完成和失败的两个回调函数被push到一个handlers数组中去了,那么这个handlers数组到底有什么意义呢?我们再回过头来看 axios/lib/core/Axios.js 文件中下面的代码。
javascript
Axios.prototype.request = function(config) {
// 对config进行处理的代码还是省略掉
// ...
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
// 请求拦截 遍历request的handlers,将每一项 fulfilled,rejected方法 加入到chain的首部
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 返回拦截 遍历response的handlers,将每一项 fulfilled,rejected方法 加入到chain的尾部
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
从上面的代码我们知道了,整个请求是一个promise,整个链是按照先执行了request的fufilled(config)和rejected(err),这个promise执行并且resolve后才会去执行dispatchRequest(config),最后执行response的fulfilled和rejected。
对于request的interceptor是先进后执行,而对于response的interceptor是先进先执行
- request和response的拦截器都可以有多对,其中每一个点都会挂在一个then()的调用上,promise.then(chain.shift(), chain.shift());
那么回到我们的问题中当存在多个拦截器interceptor1, interceptor2,那么执行顺序是
总结
当在业务中有对respons返回的数据做处理,那么最好是最后一个调用,也就是说我们的api上报拦截需要在业务拦截处理之前,这样api上报的数据是完整的。