axios接口去重、数据持久化

1,274 阅读5分钟

一、接口去重

为什么要进行接口去重?

在实际场景中,筛选面板的条件很多,用户可以频繁点击不同的筛选项,对用户不做任何防抖和节流的限制,有可能就会出现旧数据覆盖新数据。因为接口的响应时间不是固定的,比如我第一次点击,网络不好,接口5秒才返回数据,而我的第二次点击,网络很好,只需要2秒返回数据,那么实际上页面展示的数据可能是第一次调用返回的,就导致页面错乱。

怎么才能避免页面错乱?

只获取最新一次接口数据,取消其他接口请求。

1、创建存储对象

定义一个onlyDataCache对象,提供getRequestKey唯一标识、query查看、delete删除、deleteAll删除所有。唯一需要注意就是在getRequestKeyfull-path标识,主要是判断唯一标识是否为全路径,在一些特殊场景,多次请求同一个接口,传参不同,不应该并定义为相同多次请求,而是不同多次请求,所以不应该去重。

核心主要是Axios.CancelTokenCanceler方法,要取消接口请求、直接触发Canceler方法,内部会报一个Cancel异常,可以通过catch区分处理一下。怎么区分判断是否为Cancel异常?通过Axios.isCancel()API判断。

//去重缓存数据
const onlyDataCache = {
  // 缓存列表
  list: [],
  // 根据请求的信息(请求方式,url,请求get/post数据),产生map的key
  getRequestKey(config) {
    const fullPath = config.headers["full-path"];
    const { method, url, params, data } = config;
    var key = [method, url].join("&");
    if (fullPath) {
      key = [method, url, stringify(params), stringify(data)].join("&");
    }
    return key;
  },
  query() {
    console.log("onlyData=" + JSON.stringify(this.list));
  },
  add(config, cancel) {
    var key = this.getRequestKey(config);
    //console.log("onlyData add key=" + key);
    var index = this.list.findIndex(t => t.key === key);
    if (index > -1) {
      this.list[index].cancel();
      this.list[index].cancel = cancel;
    } else {
      // 添加到缓存列表中
      this.list.push({ key, cancel });
    }
  },
  delete(config) {
    var targetKey = this.getRequestKey(config);
    //console.log("onlyData delete key=" + targetKey);
    if (targetKey) {
      this.list.forEach((item, index) => {
        if (item.key === targetKey) {
          item.cancel();
          this.list.splice(index, 1);
        }
      });
    }
  },
  deleteAll() {
    this.list.forEach((item, index) => {
      item.cancel();
      this.list.splice(index, 1);
    });
  }
};

2、请求拦截去重处理

在请求接口时,会在头部headers带上一个only-data标识,主要判断当前请求是否要进行去重。先对同一个接口进行cancel()取消,并保存当前请求Canceler方法,方便下一次进行cancel()取消。

if (config.headers) {
    //判断是否需要去重
    const onlyData = config.headers["only-data"];
    if (onlyData) {
    //console.log("删除接口onlyData缓存数据!");
    onlyDataCache.delete(config);

    config.cancelToken = new Axios.CancelToken(cancel => {
     //console.log("保存接口onlyData缓存数据!");
       onlyDataCache.add(config, cancel);
    });
 }
}

3、响应拦截Cancel错误处理

为什么会出现Cancel错误?

当对接口请求就行cancel()取消,内部会生成一个Cancel类对错误信息进行包装,并抛给用户处理。如果不进行catch,控制台直接报错,虽然不影响功能使用,最好还是捕获一下。

怎么处理Cancel错误?

axios 响应拦截器中AxiosError处理。

// 所有的响应异常 区分来源为取消请求/非取消请求
if (Axios.isCancel(error)) {
   } else {
    return Promise.reject(error);
}

4、使用

调用接口时候直接在headers 传标识,默认不传为false。

headers: {
   "only-data": true
}

二、数据持久化

1、创建存储对象

定义一个keepDataCache对象,提供getRequestKey唯一标识、query查看、add增加、find查找,并且需要设置缓存数和缓存时间,在add()保存缓存数据,如果已经存在,直接覆盖旧数据,find()获取缓存数据,需判断是否存在,是否过期无效。

// 持久化缓存数据
const keepDataCache = {
  // 缓存列表
  list: [],
  // 最大缓存数
  MAX_NUM: 100,
  // 最大缓存时间
  EXPIRED_TIME: 60000,
  // 根据请求的信息(请求方式,url,请求get/post数据),产生map的key
  getRequestKey(config) {
    const { method, url, params, data } = config;
    const key = [method, url, stringify(params), stringify(data)].join("&");
    return key;
  },
  query() {
    console.log("keepData=" + JSON.stringify(this.list));
  },
  // 添加缓存结果
  add({ config, data }) {
    if (config.data) config.data = JSON.parse(config.data);
    var key = this.getRequestKey(config);
    //console.log("keepData add key=" + key);
    var index = this.list.findIndex(t => t.key === key);
    if (index > -1) {
      // 保存请求结果
      this.list[index].data = data;
    } else {
      // 添加到缓存列表中
      this.list.push({ time: Date.now(), key, data });
    }
  },
  // 查找缓存结果
  find(config) {
    // 根据请求信息生成key
    var key = this.getRequestKey(config);
    //console.log("keepData find key=" + key);
    var index = this.list.findIndex(t => t.key === key);
    // 判断缓存当中是否有该请求结果
    if (index > -1) {
      let data = this.list[index];
      // 判断是否超出了最大缓存时间
      if (Date.now() - data.time > this.EXPIRED_TIME) {
        // 清除该缓存
        this.list.splice(index, 1);
      } else {
        // 返回缓存
        return data;
      }
    }
    // 判断是否超出了最大缓存数量
    if (this.list.length === this.MAX_NUM) {
      this.list.shift();
    }
    // 返回undefined,让请求拦截不执行config.adapter
    return undefined;
  }
};

2、请求拦截获取缓存数据

在请求接口时,会在头部headers带上一个keep-data标识,主要判断当前请求是否要获取缓存。通过axios请求拦截器,设置config.adapter自定义处理请求,设置config.adapter后,请求会被拦截,不会再向服务器发请求。

  if (config.headers) {
          //判断是否需要持久化,并判断是否有缓存数据
          const keepData = config.headers["keep-data"];
          if (keepData) {
            //console.log("加载接口keepData缓存数据!");
            var cacheData = keepDataCache.find(config);
            // 查看缓存当中有没有
            if (cacheData) {
              //console.log("加载成功过,keepData有缓冲数据!");
              // 通过config.adapter,允许自定义处理请求
              config.adapter = function (config) {
                return new Promise(resolve => {
                  const res = {
                    status: 200,
                    statusText: "",
                    headers: { "content-type": "application/json" },
                    config,
                    request: {}
                  };
                  resolve({ ...res, data: cacheData.data });
                });
              };
            }
          }
    }      

3、响应拦截保存网络数据

对服务器状态、接口状态进行判断,并对数据进行缓存。

 const config = response.config;
  if (response) {
      const keepData = config.headers["keep-data"];
      if (keepData) {
          if (response.status === 200 && response.data.code === 0) {
             if (
                response.headers["content-type"] &&
                response.headers["content-type"].indexOf("application/json") >=
                  0 &&
                response.request.responseType != "blob"
              ) {
                //console.log("保存接口keepData缓存数据!");
                //缓存结果到缓存中
                keepDataCache.add(response);
              }
        }
     }
 }

4、使用

调用接口时候直接在headers 传标识,默认不传为false。

 headers: {
    "keep-data": true
 }

:就是所有接口请求,对数据进行缓存,或者获取缓存数据,都是针对于content-typeapplication/json情况下。

三、参考资料

vue阻止重复请求

vue中使用cancelToken,解决多次调用相同接口,只用保留最后一次的结果