背景
在平时项目开发中,由于历史问题,可能存在很多接口重复请求数据,带来以下问题:
- 网络资源浪费、增加服务器负载
- 页面组件重复渲染,出现闪动现象,降低用户体验
解决方案一
重构业务代码,但由于业务逻辑过多,一旦修改,容易出现线上bug,风险较高,不可取
解决方案二(最终方案)
前端请求都经过axios,在axios出添加拦截逻辑,如果在1s内发出多个相同请求,则默认只放第一个通过,其他的复用第一个的结果,可以采用axios的adapter来实现
使用方式
php
// ~request.ts
import axios, { AxiosAdapter } from 'axios';
import { createThrottleAdapter } from '@/utils/axiosThrottleAdapter';
const client = axios.create({
timeout: 30000,
baseURL: getAPIBaseUrl(),
// adapter使用方式
adapter: createThrottleAdapter(axios.defaults.adapter as AxiosAdapter, {
threshold: 1000,
}),
});
...
具体实现
typescript
// ~@/utils/axiosThrottleAdapter
import { AxiosAdapter, AxiosRequestConfig } from 'axios';
interface ThrottleAdapterOptions {
/**
* 节流间隔时间(毫秒)
*/
threshold?: number;
}
interface RequestCacheItem {
timestamp: number;
promise: Promise<any>;
}
/**
* 生成请求缓存键
* @param config axios请求配置
* @returns 缓存键
*/
const getCacheKey = (config: AxiosRequestConfig): string => {
const { method = 'get', url = '', params, data } = config;
const queryKey = params ? JSON.stringify(params) : '';
const bodyKey = data ? JSON.stringify(data) : '';
return `${method}:${url}:${queryKey}:${bodyKey}`;
};
/**
* 创建支持所有请求类型节流的适配器
* @param adapter 原始适配器
* @param options 节流选项
* @returns 增强后的适配器
*/
export const createThrottleAdapter = (adapter: AxiosAdapter, options: ThrottleAdapterOptions = {}): AxiosAdapter => {
const { threshold = 1000 } = options;
const requestCache: Record<string, RequestCacheItem> = {};
return (config: AxiosRequestConfig) => {
// 如果是OPTIONS请求或请求配置中明确禁用了节流,直接使用原始适配器
const method = (config.method || 'get').toLowerCase();
if (method === 'options' || config.throttle === false) {
return adapter(config);
}
const cacheKey = getCacheKey(config);
const now = Date.now();
// 清理过期的缓存
Object.keys(requestCache).forEach((key) => {
const { timestamp } = requestCache[key];
if (now - timestamp > threshold) {
delete requestCache[key];
}
});
// 检查缓存中是否存在未过期的请求
if (requestCache[cacheKey] && now - requestCache[cacheKey].timestamp <= threshold) {
return requestCache[cacheKey].promise;
}
// 执行请求
const promise = adapter(config);
// 更新缓存
requestCache[cacheKey] = {
timestamp: now,
promise,
};
return promise;
};
};
// 扩展AxiosRequestConfig类型
declare module 'axios' {
interface AxiosRequestConfig {
/**
* 是否禁用请求节流
*/
throttle?: boolean;
}
}
核心逻辑梳理
- 跳过节流的情况:
- OPTIONS 请求不进行节流
- 请求配置中 throttle: false 时不进行节流
- 缓存管理:
- 生成当前请求的缓存键
- 清理过期缓存项
- 检查是否存在相同的未过期请求
- 如存在,返回缓存的请求Promise
- 如不存在,执行新请求并缓存