小蚊子
高级前端工程师前端
咱们在使用 Axios 的过程当中,或多或少地要用到它的拦截器,例如要实现:ios
数据转换;
添加额外的数据;
输出或上报接口的请求时间、失败率等数据;
这些需求,使用拦截器就能很是容易地实现。那么 axios 的拦截器怎么使用,内部又是怎么实现的,这篇文章让咱们一探究竟。axios
在 axios 中,拦截器分为请求拦截器和响应拦截器。顾名思义,请求拦截器是在发出请求以前按照顺序执行的,响应拦截器是在收到响应以后(不管接口返回的是否成功)按照顺序执行的。数组
若是咱们要统计每一个接口的耗时,能够先在请求拦截器中添加一个时间戳,在响应拦截器中减去这个时间戳,就是这个请求的完整耗时:promise
// 获取当前时间 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; } );
同时,咱们还能添加多个请求拦截器和响应拦截器:markdown
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,方便在移动端进行调试;上报接口的统计数据等。前端工程师
拦截器在咱们进行接口请求时,很是的方便。那么它内部是如何实现的呢?如何维护多个拦截器并按照顺序执行的呢?dom
这里的关键文件就是 InterceptorManager.js,这里的代码也比较少,咱们一点一点地看它是怎么实现的:ide
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 个方法:学习
function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), // 请求拦截器 response: new InterceptorManager(), // 响应拦截器 }; }
将拦截器添加到 interceptors 的 request 和 response 两个属性中后,咱们就能够像上面的那样调用 use 方法添加拦截器了。request 中维护的是请求拦截器,response 中维护的是响应拦截器。
建立一个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()); }
拦截器的功能就实现啦。
咱们学习了 axios 中拦截器的思路,也能够在本身实现的一些功能组件中,使用这种机制,方便更多功能的扩展。