

axios 源码系列之拦截器的实现
source link: https://www.xiabingbao.com/post/request/axios-interceptors.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们在使用 Axios 的过程中,或多或少地要用到它的拦截器,可是axios 的拦截器怎么使用,内部又是怎么实现的?
我们在使用 Axios 的过程中,或多或少地要用到它的拦截器,例如要实现:
-
数据转换;
-
添加额外的数据;
-
输出或上报接口的请求时间、失败率等数据;
这些需求,使用拦截器就能非常容易地实现。那么 axios 的拦截器怎么使用,内部又是怎么实现的,这篇文章让我们一探究竟。
1. 拦截器的使用
在 axios 中,拦截器分为请求拦截器和响应拦截器。顾名思义,请求拦截器是在发出请求之前按照顺序执行的,响应拦截器是在收到响应之后(无论接口返回的是否成功)按照顺序执行的。
如果我们要统计每个接口的耗时,可以先在请求拦截器中添加一个时间戳,在响应拦截器中减去这个时间戳,就是这个请求的完整耗时:
// 获取当前时间 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; } );
同时,我们还能添加多个请求拦截器和响应拦截器:
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 ,这里的代码也比较少,我们一点一点地看它是怎么实现的:
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 并不区分是请求拦截器还是响应拦截器,它只是维护他自己的一组拦截器罢了。若创建多个对象,即可分别维护各自的拦截器。
function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), // 请求拦截器 response: new InterceptorManager(), // 响应拦截器 }; }
将拦截器添加到 interceptors 的 request 和 response 两个属性中后,我们就可以像上面的那样调用 use 方法添加拦截器了。request 中维护的是请求拦截器,response 中维护的是响应拦截器。
2.2 将拦截器串联起来
创建一个 chain
数组,把所有的拦截器都放进去。我们首先把真正请求接口的方法放进去:
// dispatchRequest 用于请求数据,这里我们先展示不管怎么实现的 // 这里把 dispatchRequest 也当做拦截器添加到队列中 // 每2个是一组,前面用于Promise.resolve, 后面的1个用户Promise.reject var chain = [dispatchRequest, undefined];
然后把请求拦截器放到 chain 的前面,因为我们要在发起请求之前先执行请求拦截器:
// 拦截器调用forEach方法,把每一个请求拦截器都添加到chain的前面 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { // 每2个是一组,前面用于Promise.resolve, 后面的1个用户Promise.reject // 由此也能看到,越是后添加的请求拦截器,越会是先执行 chain.unshift(interceptor.fulfilled, interceptor.rejected); });
再把响应拦截器方法 chain 的后面,因为我们要在收到响应之后才执行响应拦截器:
// 拦截器调用forEach方法,把每一个响应拦截器都添加到chain的后面 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { // 响应拦截器按照顺序执行 chain.push(interceptor.fulfilled, interceptor.rejected); });
现在已经把所有的请求拦截器、数据请求和响应拦截器都串联起来了:
然后依次执行就可以:
// 把config初始化为一个Promise对象,方便后面的使用 var promise = Promise.resolve(config); while (chain.length) { // 依次取出执行resolve和reject方法 // 将执行后的结果传给下一个拦截器 promise = promise.then(chain.shift(), chain.shift()); }
拦截器的功能就实现啦。
3. 总结
我们学习了 axios 中拦截器的思路,也可以在自己实现的一些功能组件中,使用这种机制,方便更多功能的扩展。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK