|
|
|
|
公众号矩阵

Axios 如何缓存请求数据?

本文将介绍在 Axios 中如何通过增强默认适配器来缓存请求数据。

作者:阿宝哥 来源:全栈修仙之路|2021-04-12 05:55

本文转载自微信公众号「全栈修仙之路」,作者阿宝哥。转载本文请联系全栈修仙之路公众号。 

在 Axios 如何取消重复请求? 这篇文章中,阿宝哥介绍了在 Axios 中如何取消重复请求及 CancelToken 的工作原理。本文将介绍在 Axios 中如何通过增强默认适配器来缓存请求数据。那么为什么要缓存请求数据呢?这是因为在缓存未失效时,我们可以直接使用已缓存的数据,而不需发起请求从服务端获取数据,这样不仅可以减少 HTTP 请求而且还能减少等待时间从而提高用户体验。

因为本文将使用 Axios 提供的默认适配器来实现缓存请求数据的功能,所以如果你对 Axios 适配器还不熟悉的话,建议先阅读 77.9K 的 Axios 项目有哪些值得借鉴的地方 这篇文章。为了让大家能够更好地理解后续的内容,我们先来看一下整体的流程图:

上图中蓝色部分的工作流程,就是本文的重点。接下来,阿宝哥将从如何设计缓存开始,带大家一起来开发缓存请求数据的功能。

一、如何设计缓存

在计算中,缓存是一个高速数据存储层,其中存储了数据子集,且通常是 短暂性 存储,这样日后再次请求该数据时,速度要比访问数据的主存储位置快。通过缓存,你可以高效地重用之前检索或计算的数据。了解完缓存的作用之后,我们来设计缓存的 API:

  • get(key):从缓存中获取指定 key 对应的值;
  • delete(key):从缓存中删除指定 key 对应的值;
  • clear():清空已缓存的数据;
  • set(key, value, maxAge):保存键值对,同时支持设置缓存的最大时间,即 maxAge 单位为毫秒。

基于上述的缓存 API,我们可以实现一个简单的缓存功能,具体代码如下所示:

  1. const MemoryCache = { 
  2.   data: {}, 
  3.   set(key, value, maxAge) { // 保存数据 
  4.     this.data[key] = { 
  5.       maxAge: maxAge || 0, 
  6.       value, 
  7.       now: Date.now(), 
  8.      }; 
  9.   }, 
  10.   get(key) { // 从缓存中获取指定 key 对应的值。 
  11.     const cachedItem = this.data[key]; 
  12.     if (!cachedItem) return null
  13.     const isExpired = Date.now() - cachedItem.now > cachedItem.maxAge; 
  14.     isExpired && this.delete(key); 
  15.     return isExpired ? null : cachedItem.value; 
  16.   }, 
  17.   delete(key) { // 从缓存中删除指定 key 对应的值。 
  18.     return delete this.data[key]; 
  19.   }, 
  20.   clear() { // 清空已缓存的数据。 
  21.     this.data = {}; 
  22.   }, 
  23. }; 

其实除了自定义缓存对象之外,你也可以使用成熟的第三方库,比如 lru-cache。

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

二、如何增强默认适配器

Axios 引入了适配器,使得它可以同时支持浏览器和 Node.js 环境。对于浏览器环境来说,它通过封装 XMLHttpRequest API 来发送 HTTP 请求,而对于 Node.js 环境来说,它通过封装 Node.js 内置的 http 和 https 模块来发送 HTTP 请求。

在介绍如何增强默认适配器之前,我们先来回顾一下 Axios 完整请求的流程:

了解完 Axios 完整请求的流程之后,我们再来看一下 Axios 内置的 xhrAdapter 适配器,它被定义在 lib/adapters/xhr.js 文件中:

  1. // lib/adapters/xhr.js 
  2. module.exports = function xhrAdapter(config) { 
  3.   return new Promise(function dispatchXhrRequest(resolve, reject) { 
  4.     var requestData = config.data; 
  5.     var requestHeaders = config.headers; 
  6.  
  7.     var request = new XMLHttpRequest(); 
  8.     // 省略大部分代码 
  9.     var fullPath = buildFullPath(config.baseURL, config.url); 
  10.     request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); 
  11.     // Set the request timeout in MS 
  12.     request.timeout = config.timeout; 
  13.  
  14.     // Listen for ready state 
  15.     request.onreadystatechange = function handleLoad() { ... } 
  16.  
  17.     // Send the request 
  18.     request.send(requestData); 
  19.   }); 
  20. }; 

很明显 xhrAdapter 适配器是一个函数对象,它接收一个 config 参数并返回一个 Promise 对象。而在 xhrAdapter 适配器内部,最终会使用 XMLHttpRequest API 来发送 HTTP 请求。为了实现缓存请求数据的功能,我们就可以考虑通过高阶函数来增强 xhrAdapter 适配器的功能。

2.1 定义辅助函数

2.1.1 定义 generateReqKey 函数

在增强 xhrAdapter 适配器之前,我们先来定义一个 generateReqKey 函数,该函数用于根据当前请求的信息,生成请求 Key;

  1. function generateReqKey(config) { 
  2.   const { method, url, params, data } = config; 
  3.   return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&"); 

通过 generateReqKey 函数生成的请求 key,将作为缓存项的 key,而对应的 value 就是默认 xhrAdapter 适配器返回的 Promise 对象。

2.1.2 定义 isCacheLike 函数

isCacheLike 函数用于判断传入的 cache 参数是否实现了前面定义的 Cache API,利用该函数,我们允许用户为每个请求自定义 Cache 对象。

  1. function isCacheLike(cache) { 
  2.  return !!(cache.set && cache.get && cache.delete && cache.clear   
  3.   && typeof cache.get === 'function' && typeof cache.set === 'function'  
  4.     && typeof cache.delete === 'function' && typeof cache.clear === 'function' 
  5.   ); 

2.2 定义 cacheAdapterEnhancer 函数

为了让用户能够更灵活地控制数据缓存的功能,我们定义了一个 cacheAdapterEnhancer 函数,该函数支持两个参数:

adapter:预增强的 Axios 适配器对象;

options:缓存配置对象,该对象支持 4 个属性,分别用于配置不同的功能:

maxAge:全局设置缓存的最大时间;

enabledByDefault:是否启用缓存,默认为 true;

cacheFlag:缓存标志,用于配置请求 config 对象上的缓存属性;

defaultCache:用于设置使用的缓存对象。

了解完 cacheAdapterEnhancer 函数的参数之后,我们来看一下该函数的具体实现:

  1. function cacheAdapterEnhancer(adapter, options) { 
  2.   const { maxAge, enabledByDefault = true
  3.     cacheFlag = "cache", defaultCache = MemoryCache, 
  4.   } = options; 
  5.    
  6.   return (config) => { 
  7.     const { url, method, params, forceUpdate } = config; 
  8.     let useCache = config[cacheFlag] !== undefined && config[cacheFlag] !== null 
  9.         ? config[cacheFlag] 
  10.         : enabledByDefault; 
  11.       if (method === "get" && useCache) { 
  12.         const cache = isCacheLike(useCache) ? useCache : defaultCache; 
  13.         let requestKey = generateReqKey(config);  // 生成请求Key 
  14.         let responsePromise = cache.get(requestKey); // 从缓存中获取请求key对应的响应对象 
  15.         if (!responsePromise || forceUpdate) { // 缓存未命中/失效或强制更新时,则重新请求数据 
  16.            responsePromise = (async () => { 
  17.              try { 
  18.                return await adapter(config);  // 使用默认的xhrAdapter发送请求 
  19.              } catch (reason) { 
  20.                  cache.delete(requestKey); 
  21.                  throw reason; 
  22.                 } 
  23.            })(); 
  24.            cache.set(requestKey, responsePromise, maxAge);  // 保存请求返回的响应对象 
  25.            return responsePromise; // 返回已保存的响应对象 
  26.        } 
  27.        return responsePromise; 
  28.      } 
  29.      return adapter(config); // 使用默认的xhrAdapter发送请求 
  30.    }; 

以上的代码并不会复杂,核心的处理逻辑如下图所示:

2.3 使用 cacheAdapterEnhancer 函数

2.3.1 创建 Axios 对象并配置 adapter 选项

  1. const http = axios.create({ 
  2.   baseURL: "https://jsonplaceholder.typicode.com"
  3.   adapter: cacheAdapterEnhancer(axios.defaults.adapter, { 
  4.     enabledByDefault: false, // 默认禁用缓存 
  5.     maxAge: 5000, // 缓存时间为5s 
  6.   }), 
  7. }); 

2.3.2 使用 http 对象发送请求

  1. // 使用缓存 
  2. async function requestWithCache() { 
  3.   const response = await http.get("/todos/1", { cache: true }); 
  4.   console.dir(response); 
  5.  
  6. // 不使用缓存 
  7. async function requestWithoutCache() { 
  8.   const response = await http.get("/todos/1", { cache: false }); 
  9.   console.dir(response); 

其实 cache 属性除了支持布尔值之外,我们可以配置实现 Cache API 的缓存对象,具体的使用示例如下所示:

  1. const customCache = { get() {/*...*/}, set() {/*...*/}, delete() {/*...*/}, clear() {/*...*/}}; 
  2.        
  3. async function requestForceUpdate() { 
  4.   const response = await http.get("/todos/1", { 
  5.     cache: customCache, 
  6.     forceUpdate: true
  7.   }); 
  8.   console.dir(response); 

好了,如何通过增强 xhrAdapter 适配器来实现 Axios 缓存请求数据的功能已经介绍完了。由于完整的示例代码内容比较多,阿宝哥就不放具体的代码了。感兴趣的小伙伴,可以访问以下地址浏览示例代码。

完整的示例代码:https://gist.github.com/semlinker/b8a7bd5a0a16c2d04011c2c4a8167fbd

三、总结

本文介绍了在 Axios 中如何缓存请求数据及如何设计缓存对象,基于文中定义的 cacheAdapterEnhancer 函数,你可以轻松地扩展缓存的功能。在后续的文章中,阿宝哥将会介绍在 Axios 中如何实现请求重试功能,感兴趣的小伙伴不要错过哟。

【编辑推荐】

  1. 大数据开发:JAVA线程与进程区别是这样?
  2. 微软主动删除了人脸识别数据库有何玄机?
  3. 用大数据提升宏观经济治理效能
  4. 秀啊,用Python快速开发在线数据库更新修改工具
  5. Mysql数据库详解
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
24H热文
一周话题
本月获赞

订阅专栏+更多

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

7人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

37人订阅学习

数据中心和VPDN网络建设案例

数据中心和VPDN网络建设案例

漫画+案例
共20章 | 捷哥CCIE

231人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微