利用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()的方式来调用。

 

 

 

posted @ 2021-04-21 16:51  放飞的回忆  阅读(201)  评论(0编辑  收藏  举报