Skip to content

Commit

Permalink
add axios-cache util
Browse files Browse the repository at this point in the history
  • Loading branch information
blesstosam committed Apr 15, 2020
1 parent 602c4d1 commit a5ffc92
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 48 deletions.
91 changes: 88 additions & 3 deletions src/api/axios-cache.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,88 @@
// todo
// 1. 请求接口的时候先查找缓存 如果缓存存在并且没有失效 则使用缓存 如果失效继续请求接口 - 缓存失效时间可以配置在new 实例的时候设置
// 2. 默认使用缓存 在new实例的时候可以强制关闭缓存 也可以在单个接口中开启和关闭缓存
// 1. 请求接口的时候先查找缓存 如果缓存存在并且没有失效 则使用缓存 如果失效继续请求接口
// 2. 默认关闭缓存 调用插件的时候可以控制开启关闭缓存 也可以在单个接口中开启和关闭缓存
// 3. 单个请求可以强制使用缓存或丢弃缓存 ignoreCache
// 4. store 可以为变量也可以为localstorage redis 等

import { HttpService, generateHashKey } from './util';
import { AxiosRequestConfig, AxiosResponse } from 'axios';
import localStore from '@/assets/script/storage';

// 拦截器里后use的函数先执行 是栈
/**
* @param httpClient
* @param param1 ttl 缓存失效时间 单位 ms
* @param store 将store分离出去 可以选择
* 变量缓存(刷新浏览器即丢失)- 默认使用
* localstorage|sessionstorage 本地缓存
* @param exclude todo
* {
* paths: [], // url 路径
* methods: [], // 请求方式
* filter: () => boolean // 为true的时候就不开启
* }
*/
export function axiosCache(
httpClient: HttpService,
{
ttl,
store = new Store(),
exclude = {}
}: {
ttl: number;
store?: Store | typeof localStore;
exclude?: { paths?: string[]; methods?: Array<string>; filter?: () => boolean };
}
) {
httpClient.service.interceptors.request.use((config: AxiosRequestConfig) => {
const { paths, methods, filter } = exclude;
if (paths && paths.includes(config.url || '')) return config;
if (methods && methods.includes(config.method as string)) return config;
if (filter && filter()) return config;

const hash = generateHashKey(config);
if (store.has(hash)) {
const { expire, data } = store.get(hash)!;
if (expire > Date.now()) {
// 未过期 必须包装成 {data:data} => response
// 如果reslove还需要执行下一个request拦截器 不行
// 如果是reject axios会读取cancelToken 报错
// 所以统一用canceltoken来取消请求
return Promise.reject({ data });
}
}

// @ts-ignore
config.__cacheHash__ = hash;
return config;
});

httpClient.service.interceptors.response.use((response: AxiosResponse) => {
// 将data保存在store里
if (response.status === 200) {
// @ts-ignore
const cacheHash = response.config.__cacheHash__;
store.set(cacheHash, { expire: Date.now() + ttl, data: response.data });
}

return response;
});
}

export class Store {
store: Map<string, { data: any; expire: number }>;
constructor() {
this.store = new Map();
}

get(key: string) {
return this.store.get(key);
}

has(key: string) {
return this.store.has(key);
}

set(key: string, d: { data: any; expire: number }) {
return this.store.set(key, d);
}
}
64 changes: 31 additions & 33 deletions src/api/axios-retry.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
// 1. 重新请求接口 可以在new HttpService() 之后使用
// const httpClinet = new HttpService()
// axiosRetry(httpClinet, {times: 2, enable: true})
// axiosRetry(httpClinet, {times: 2 })
// 2. todo - 在单个接口请求中开启retry并指定retry次数 post 和 get 方法多传一个参数

// todo need to test!!!
import { HttpService } from './util';
import { AxiosResponse, AxiosError } from 'axios';
export function axiosRetry(
httpClient: HttpService,
{ times = 2, enable = false }: { times: number; enable: boolean }
) {
if (enable) {
let retryTimes = 0;
httpClient.service.interceptors.response.use(
(response: AxiosResponse) => {
retryTimes = 0;
return response;
},
(error: AxiosError) => {
if (error.response) {
if (retryTimes < times) {
retryTimes++;
const resp = error.response;
const method = resp.config.method;
const url = resp.config.url;
let params, fn;
if (method === 'get') {
params = resp.config.params || {};
fn = httpClient.get;
} else if (method === 'post') {
params = resp.config.data || {};
fn = httpClient.post;
}

if (url && params && fn) {
fn(url, params);
}
}
/**
*
* @param httpClient axios 实例
* @param param1 times: 重复请求次数
*/
export function axiosRetry(httpClient: HttpService, { times = 2 }: { times: number }) {
let retryTimes = 0;
httpClient.service.interceptors.response.use(
(response: AxiosResponse) => {
retryTimes = 0;
return response;
},
(error: AxiosError) => {
if (retryTimes < times) {
retryTimes++;
const { method, url } = error.config;
let params, fn;
if (method === 'get') {
params = error.config.params || {};
fn = httpClient.get;
} else if (method === 'post') {
params = error.config.data || {};
fn = httpClient.post;
}

if (url && params && fn) {
fn(url, params);
}
}
);
}
}
);
}
29 changes: 17 additions & 12 deletions src/api/util.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// 1. enableCanclePedding 处于 pendding 的请求默认被拦截
// 2. retryOpts {times: 1, enable: false} 默认关闭retry 开启retry后默认多请求一次
// 2. retryOpts {times: 2 } 默认关闭retry 开启retry后请求2次
// 3. cache todo
import axios, { AxiosResponse, AxiosRequestConfig, AxiosError, AxiosInstance } from 'axios';
import store from '../store';
import { LOGOUT } from '@/store/mutation-types';
import Vue from 'vue';
import cfg from '@/config/index';
import sha256 from 'sha256';
import { paramToUrl } from '@/assets/script/util';

let dispathLogoutTime = 0; // 确保logout只调用了一次
let apiPeddingMap: Map<string, boolean> = new Map(); // 正在pendding的接口请求
Expand All @@ -29,15 +30,24 @@ const removePenddingHash = (d: AxiosResponse | AxiosError | unknown) => {
}
};

/**
* 根据请求生成唯一的hash值作为key
* @param config
*/
export function generateHashKey(config: AxiosRequestConfig) {
const { url, method, params, data } = config;
const param = method === 'get' ? params : data;
const hash = sha256(method + ':' + paramToUrl(url || '', param));
return hash;
}

export class HttpService {
service: AxiosInstance;

constructor(
url: string,
// 是否开启pendding请求的拦截 默认为true
enableCanclePedding: boolean = true,
requestCb?: (config: AxiosRequestConfig) => void,
responseCb?: (response: AxiosResponse) => void
enableCanclePedding: boolean = true
) {
this.service = axios.create({
baseURL: url,
Expand All @@ -54,15 +64,11 @@ export class HttpService {
const _locale = (store.state as any).app.local;
config.headers.locale = _locale === cfg.langType.CN ? 'zh_CN' : 'en_US';

// 将单独的逻辑通过回调的方式给外部处理
requestCb && requestCb(config);

// 每次请求前 先判断该请求是否在pedding中 如果在拦截掉该请求
// 不在则请求,并将api请求放到pendding里
if (enableCanclePedding) {
const { url, method, params, data } = config;
const param = method === 'get' ? params : data;
const hash = sha256((url || '') + method + JSON.stringify(param));
const { url, method } = config;
const hash = generateHashKey(config);
if (apiPeddingMap.has(hash)) {
return Promise.reject(
new Error(`Request ${method}-${url} is in pendding, do not request again!`)
Expand Down Expand Up @@ -108,8 +114,6 @@ export class HttpService {
return Promise.reject(response); // 407的直接不处理
}

responseCb && responseCb(response);

return response; // return 会默认resolve
},
(error: AxiosError) => {
Expand Down Expand Up @@ -174,6 +178,7 @@ export class HttpService {
reslove(res.data);
},
err => {
window.console.error(err);
reslove(err);
}
);
Expand Down
10 changes: 10 additions & 0 deletions src/assets/script/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const api = {

remove(key: string, storage: Storage) {
storage.removeItem(key);
},

has(key: string, storage: Storage) {
return storage.has(key);
}
};

Expand All @@ -32,6 +36,9 @@ const store = {
get(key: string) {
return api.get(key, this.storage);
},
has(key: string) {
return api.has(key, this.storage);
},
remove(key: string) {
return api.remove(key, this.storage);
},
Expand All @@ -43,6 +50,9 @@ const store = {
get(key: string) {
return api.get(key, this.storage);
},
has(key: string) {
return api.has(key, this.storage);
},
remove(key: string) {
return api.remove(key, this.storage);
}
Expand Down

0 comments on commit a5ffc92

Please sign in to comment.