利用promise和装饰器封装一个缓存api请求的装饰器工具
实际情景
在网页实际运行当中存在很多接口的数据在一定时间都不会变动,然而却要经常请求,这样就会带来后台服务器的巨大压力,也为前端数据请求的效率带来很大的影响。像这种数据不会变动的接口占用了太多带宽,因此这些接口的数据最好缓存起来。在前端 api 请求缓存方案的基础上结合自己的实际需求所封装的一个缓存api请求的装饰器工具。
封装的工具
封装装饰器(JS 装饰器,一篇就够)
// 制定修饰器 export default function ApiCache(...args) { return decorate(handleApiCache, args); } function decorate(handleDescription: any, entryArgs: any) { // 判断当前最后数据是否是descriptor,如果是descriptor,直接使用 // 例如 log 这样的修饰器 if (isDescriptor(entryArgs[entryArgs.length - 1])) { return handleDescription(...entryArgs); } else { // 如果不是 // 例如 add(1) plus(20) 这样的修饰器 return function() { return handleDescription(...Array.prototype.slice.call(arguments), ...entryArgs); }; } } function isDescriptor(descriptor: any) { if (descriptor) { return ( descriptor.hasOwnProperty('configurable') && descriptor.hasOwnProperty('enumerable') && descriptor.hasOwnProperty('writable') && descriptor.hasOwnProperty('value') ); } return false; } function handleApiCache(target: any, name: any, descriptor: any, ...config: any) { // 拿到函数体并保存 const fn = descriptor.value; // 修改函数体 descriptor.value = function() { const key = generateKey(name, arguments[0]); let promise = ExpiresCache.get(key); if (!promise) { // 设定promise promise = fn.apply(null, arguments).catch((error: any) => { // 在请求回来后,如果出现问题,把promise从cache中删除 ExpiresCache.delete(key); // 返回错误 return Promise.reject(error); }); // 使用缓存,缓存过期之后再次get就会获取null,而从服务端继续请求 ExpiresCache.set(key, promise, config[0]); // console.log(ExpiresCache.cacheMap); } return promise; }; return descriptor; } // 生成key值 function generateKey(name: string, config: any) { return encodeURIComponent(`${name}-${JSON.stringify(config.data)}`); } class ItemCache { private data: any; private timeout: number; private cacheTime: number; constructor(data: any, timeout: number) { this.data = data; // 设定超时时间,设定为多少秒 this.timeout = timeout; // 创建对象时候的时间,大约设定为数据获得的时间 this.cacheTime = new Date().getTime(); } } class ExpiresCache { // 定义静态数据map来作为缓存池 static cacheMap = new Map(); // 数据是否超时 static isOverTime(name: any) { const data = ExpiresCache.cacheMap.get(name); // 没有数据 一定超时 if (!data) { return true; } // 获取系统当前时间戳 const currentTime = new Date().getTime(); // 获取当前时间与存储时间的过去的秒数 const overTime = (currentTime - data.cacheTime) / 1000; // 如果过去的秒数大于当前的超时时间,也返回null让其去服务端取数据 if (Math.abs(overTime) > data.timeout) { // 此代码可以没有,不会出现问题,但是如果有此代码,再次进入该方法就可以减少判断。 ExpiresCache.cacheMap.delete(name); return true; } // 不超时 return false; } // 删除 cache 中的 data static delete(name: string | Error) { return ExpiresCache.cacheMap.delete(name); } // 获取 static get(name: string | Error) { const isDataOverTime = ExpiresCache.isOverTime(name); // 如果 数据超时,返回null,但是没有超时,返回数据,而不是 ItemCache 对象 return isDataOverTime ? null : ExpiresCache.cacheMap.get(name).data; } // 默认存储60分钟 static set(name: string | Error, data: any, timeout = 3600) { // 设置 itemCache const itemCache = new ItemCache(data, timeout); // 缓存 ExpiresCache.cacheMap.set(name, itemCache); } }
给api请求添加上装饰器
import Api from '@/utils/request'; import ApiCache from '@/utils/apiCache';
// 因为装饰器只能装饰类和类里面的方法,因此要把api请求放到类当中 class Common { @ApiCache tree(params = {}) { return Api.tree(params); } } export const common = new Common();
页面当中实际调用
import { common } from '@/api/common'; export default class Tree extends Vue { async getTreeData() { let[err, data] = await this.$to(common.tree({ data: {} })); if (err) { return; } } }
至此一个缓存api请求的装饰器工具已经封装好了,在第一次请求时会向后台发送请求,之后只要是页面不是手动刷新,在vue当中在缓存时间内就会一直存在,可以通过promise.then()的方式来调用。