大鹿
Jiann
Do not go gentle into that good night. 🏕️

接口缓存层设计与实现

Why

问题点

  • 跨组件、跨项目的重复请求较多 要求
  • 对代码侵入性小,使用简单
  • 支持各种框架

技术目标

缓存方案

  • 内存
  • webworker,
  • localstorage,
  • sessionstorage,
  • indexdb

需要缓存的类型

  1. 同页面内相同接口重复请求
  2. 同页面内相同接口,不同参数的合并请求,如多条数据基于id的补全
  3. 微前端下不同tab页的相同请求
  4. 浏览器tab页的数据请求

交互形式

  • 单页内重复请求过滤
  • 微前端跨tab页实现
  • 页面聚焦刷新
  • 本地缓存更新
  • ...

业内的实现方案

项目中实现

基于请求参数缓存实现

/**
 * promise缓存函数,默认会基于入参作为key进行缓存进行
 * @param fun 返回值为promise的函数
 * @returns 返回值为promise的函数

// demo 创建promise缓存 
import { promiseCacher } from '@bui/lcp-tools';
import axios from 'axios';

function get(params) {
  return axios.get('http://dev.xuelei.com/static/js/area/new.json')
}
const getData = promiseCacher(get);

// 调用
getData().then(res => {
  console.log('1111', res);
});
getData().then(res => {
  console.log('22222', res);
});
setTimeout(() => {
  getData().then(res => {
    console.log('3333', res);
  });
}, 2000);
 */

import Events from 'events';

enum PromiseStatus {
  Pending = 'pending',
  Fulfilled = 'fulfilled',
  Rejected = 'rejected',
}

export function promiseCacher<T, A>(fun: (...arg: any) => Promise<T>): Promise<T> {
  const eventEmitter = new Events.EventEmitter();
  const cache: Map<any, { state?: PromiseStatus; value?: any }> = new Map();

  return (...arg: any[]) => {
    const key = JSON.stringify(arg);
    const result = cache.get(key) || {};

    if (result.state === PromiseStatus.Fulfilled) {
      return Promise.resolve(result.value);
    }
    if (result.state === PromiseStatus.Rejected) {
      return Promise.reject(result.value);
    }

    if (!result.state) {
      result.state = PromiseStatus.Pending;
      cache.set(key, result);
      fun(...arg).then((v: any) => {
        result.state = PromiseStatus.Fulfilled;
        result.value = v;
        eventEmitter.emit(`resolve-${key}`, result.value);
      }).catch((e: any) => {
        result.state = PromiseStatus.Rejected;
        result.value = e;
        eventEmitter.emit('reject' + key, e);
      }).finally(() => {
        cache.set(key, result);
      });
    }

    return new Promise((resolve, reject) => {
      eventEmitter.once(`resolve-${key}`, resolve);
      eventEmitter.once(`reject-${key}`, reject);
    });
  };
}