axios 源码系列之拦截器的实现

蚊子前端博客
发布于 2020-11-14 22:06
我们在使用 Axios 的过程中,或多或少地要用到它的拦截器,可是axios 的拦截器怎么使用,内部又是怎么实现的?

我们在使用 Axios 的过程中,或多或少地要用到它的拦截器,例如要实现:

  1. 数据转换;

  2. 添加额外的数据;

  3. 输出或上报接口的请求时间、失败率等数据;

这些需求,使用拦截器就能非常容易地实现。那么 axios 的拦截器怎么使用,内部又是怎么实现的,这篇文章让我们一探究竟。

1. 拦截器的使用

在 axios 中,拦截器分为请求拦截器和响应拦截器。顾名思义,请求拦截器是在发出请求之前按照顺序执行的,响应拦截器是在收到响应之后(无论接口返回的是否成功)按照顺序执行的。

如果我们要统计每个接口的耗时,可以先在请求拦截器中添加一个时间戳,在响应拦截器中减去这个时间戳,就是这个请求的完整耗时:

COPYJAVASCRIPT

// 获取当前时间 const getTime = () => { if (typeof performance?.now === "function") { return window.performance.now(); } return Date.now(); }; // 接口上报 const reportCgi = (response, config) => { // 响应失败时response为空 const { config: conf } = response || { config }; // 在响应拦截器中计算这个请求的耗时 console.log("response", conf.url, getTime() - conf.requestime); }; axios.interceptors.request.use((config) => { // 在请求拦截器中添加发起请求的时间 return { ...config, ...{ requesttime: getTime() } }; }); axios.interceptors.response.use( (response) => { reportCgi(response); return response; }, (error) => { reportCgi(error.response, error.config); return error; } );

同时,我们还能添加多个请求拦截器和响应拦截器:

COPYJAVASCRIPT

axios.interceptors.request.use((config) => { // 这里假设要先获取一个token return new Promise((resolve) => { setTimeout(() => { resolve({ ...config, ...{ token: Math.random() } }); }, 500); }); }); axios.interceptors.request.use((config) => { // 在请求拦截器中添加发起请求的时间 return { ...config, ...{ requesttime: getTime() } }; });

除此之外,axios 的拦截器还能做很多事情,如输出请求 log 和响应 log,方便在移动端进行调试;上报接口的统计数据等。

2. 拦截器是怎么实现的

拦截器在我们进行接口请求时,非常的方便。那么它内部是如何实现的呢?如何维护多个拦截器并按照顺序执行的呢?

2.1 拦截器的实现

这里的关键文件就是 InterceptorManager.js,这里的代码也比较少,我们一点一点地看它是怎么实现的:

COPYJAVASCRIPT

var utils = require("./../utils"); function InterceptorManager() { // 存储所有的拦截器,但请求拦截器和响应拦截器是分开的 this.handlers = []; } /** * 添加拦截器 * fulfilled: 成功时执行的,在Promise.resolve中 * rejected: 失败时执行的,在Promise.reject中 * * 返回当前添加的拦截器的ID,用于清除这个拦截器 */ InterceptorManager.prototype.use = function use(fulfilled, rejected) { // 把传入的在resolve和reject中要执行的方法添加到数组中 this.handlers.push({ fulfilled: fulfilled, rejected: rejected, }); return this.handlers.length - 1; }; /** * 根据id请求拦截器 * * id: 刚才use方法返回的那个数据 */ InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; /** * 迭代所有的拦截器 * * 这里会跳过之前使用eject方法设置为null的拦截器 * * @param {Function} fn 对所有拦截器都执行的一个方法 */ InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; module.exports = InterceptorManager;

InterceptorManager 维护着 handlers 里面所有的拦截器,对外提供了 3 个方法:

  • use: 添加拦截器,接受 2 个参数,一个是 Promise 成功时执行的,第 2 个时 Promise 失败时执行的;

  • eject: 根据 id 清除这个拦截器;

  • forEach: 循环所有的拦截器,并跳过所有为空的拦截器;

InterceptorManager 并不区分是请求拦截器还是响应拦截器,它只是维护他自己的一组拦截器罢了。若创建多个对象,即可分别维护各自的拦截器。

COPYJAVASCRIPT

function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), // 请求拦截器 response: new InterceptorManager(), // 响应拦截器 }; }

将拦截器添加到 interceptors 的 request 和 response 两个属性中后,我们就可以像上面的那样调用 use 方法添加拦截器了。request 中维护的是请求拦截器,response 中维护的是响应拦截器。

2.2 将拦截器串联起来

创建一个chain数组,把所有的拦截器都放进去。我们首先把真正请求接口的方法放进去:

COPYJAVASCRIPT

// dispatchRequest 用于请求数据,这里我们先展示不管怎么实现的 // 这里把 dispatchRequest 也当做拦截器添加到队列中 // 每2个是一组,前面用于Promise.resolve, 后面的1个用户Promise.reject var chain = [dispatchRequest, undefined];

然后把请求拦截器放到 chain 的前面,因为我们要在发起请求之前先执行请求拦截器:

COPYJAVASCRIPT

// 拦截器调用forEach方法,把每一个请求拦截器都添加到chain的前面 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { // 每2个是一组,前面用于Promise.resolve, 后面的1个用户Promise.reject // 由此也能看到,越是后添加的请求拦截器,越会是先执行 chain.unshift(interceptor.fulfilled, interceptor.rejected); });

再把响应拦截器方法 chain 的后面,因为我们要在收到响应之后才执行响应拦截器:

COPYJAVASCRIPT

// 拦截器调用forEach方法,把每一个响应拦截器都添加到chain的后面 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { // 响应拦截器按照顺序执行 chain.push(interceptor.fulfilled, interceptor.rejected); });

现在已经把所有的请求拦截器、数据请求和响应拦截器都串联起来了:

axios的拦截器-蚊子的前端博客

然后依次执行就可以:

COPYJAVASCRIPT

// 把config初始化为一个Promise对象,方便后面的使用 var promise = Promise.resolve(config); while (chain.length) { // 依次取出执行resolve和reject方法 // 将执行后的结果传给下一个拦截器 promise = promise.then(chain.shift(), chain.shift()); }

拦截器的功能就实现啦。

3. 总结

我们学习了 axios 中拦截器的思路,也可以在自己实现的一些功能组件中,使用这种机制,方便更多功能的扩展。

标签:
阅读(1309)

公众号:

qrcode

微信公众号:前端小茶馆