为axios添加基于window的缓存能力

业务诉求 有些业务对时效性要求并不高,可以通过给接口增加基于window的缓存能力,即在一定时间内相同的请求复用之前的请求结果,来实现页面的快速展现。比如 页面中有些图表,可能底层是一个接口的数据,但每个图表对不同的指标进行聚合运算。倘若将数据查询也都封装到chart内部,结合数据缓存,可以使得每个图表功能高内聚且不影响性能; 查看当前页面时,又返回之前的页面,倘若需要再等待一次请求,可能会有些考验耐心吧 axios interceptor && adapter axios interceptors 是我们常用的拦截器,通常用来对request统一添加Header,对response统一处理error,axios文档上也有示例代码。为了支持对请求结果的代理拦截,还需要adapter参数来配合。 adapter介绍 axios对adapter参数是如此介绍的: adapter allows custom handling of requests which makes testing easier. // Return a promise and supply a valid response (see lib/adapters/README.md). adapter: function (config) { /* ... */ }, adapter可以用来对特定请求,返回一个自定义的promise.所以首先约定对哪些请求进行缓存, 然后response中能够识别这些请求,并且进行缓存。 首先需要知道:request中配置的config默认都是会透传给response的,也就是每个请求的的request/response而言,两者是能获取到同一份config的。 所以可以约定在config中定义额外参数cache,如果有传值,则认为是需要缓存的。在response拦截时,对于config中有设置cache参数的请求结果进行缓存。 缓存设置精细化 对每个请求进行缓存,其存储形式为: 将能够表示每个请求独特性的参数: url, method, params(data) stringify之后作为key value中存放3个属性: data: response data pending: 是否正在请求 expire: 过期时间 这样设计可以满足:1. 自定义请求的缓存时间; 2. 同时发出多个相同请求,只会实际发出一个网络请求,其余等着返回结果公用。另外,对于配置了cache的请求,还需要支持另外一个参数forceUpdate,因为可能在缓存期间,需要拉去最新数据,比如列表页中完成创建任务后。 代码如下: /** * * 在axios config中添加了cache, 单位秒。 * 与之配合的有forceUpdate,使用场景:列表页新增之后的refetch需要从接口拉去最新数据,此时就需要添加该参数 * 对相同参数(url与params/data的组合)的请求,只会实际请求一次。 * */ import axios from 'axios'; import EventEmitter from 'yourEventEmitter'; // 随意一个eventEmiter就好,就用到了emit和once方法 const event = new EventEmitter(); const cacheData = Symbol('window_cache'); window[cacheData] = {}; function getCacheKey(config = {}) { let reqParams = {}; const { method, params, data } = config; const reqData = method === 'get' ? params : data; if (typeof reqData === 'string') { try { reqParams = JSON.parse(reqData); } catch (err) { console.error('parse cacheKey error:: ', err); } } else { reqParams = reqData; } const reqKey = { url: config.url, params: reqParams, method, }; let key; try { key = btoa(JSON.stringify(reqKey)); } catch (err) { console.error('btoa error::', err); key = JSON.stringify(reqKey); } return key; } axios.interceptors.request.use( function(config) { const { cache, forceUpdate } = config; if (cache) { const paramsKey = getCacheKey(config); if (!window[cacheData][paramsKey]) { window[cacheData][paramsKey] = {}; } const { data, pending, expire } = window[cacheData][paramsKey]; if (pending) { config.adapter = () => { return new Promise(resolve => { event.once(paramsKey, resData => resolve({ data: resData, status: config.status, statusText: config.statusText, headers: config.headers, config: { ...config, useCache: true, }, request: config, }), ); }); }; } else if (!forceUpdate && data && expire && Date.now() < expire) { config.adapter = () => { return Promise.resolve({ data, status: config.status, statusText: config.statusText, headers: config.headers, config: { ...config, useCache: true, }, request: config, }); }; } else { window[cacheData][paramsKey].pending = true; } } return config; }, function(error) { return Promise.reject(error); }, ); axios.interceptors.response.use( function(response) { const { config = {}, data: resOriginData } = response; const { errNo } = resOriginData; const { cache, useCache } = config; // 只对接口请求缓存,从缓存读取时不再更新,也防止expire不断延期 if (cache && !useCache) { const paramsKey = getCacheKey(config); const resData = response.data; // 需缓存的接口,请求失败时不缓存结果 if (errNo !== 0) { window[cacheData][paramsKey] = { pending: false, }; } else { window[cacheData][paramsKey] = { data: resData, pending: false, expire: Date.now() + cache * 1000, }; } event.emit(paramsKey, resData); } return response; }, function(error) { // 自定义错误处理 return Promise.reject(error); }, ); export default axios;

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):