第一版代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import axios from 'axios'
import LRUCache from 'lru-cache'
const cache = new LRUCache({
max: 100,
maxAge: 10000
});
const useCache = config => {
if (!config?.useCache) return config;
// 生成缓存key
const cacheKey = getCacheKey(config);
// 如果缓存中有结果,则直接返回缓存结果
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
let source = axios.CancelToken.source();
config.cancelToken = source.token;
// @ts-ignore
source.cancel({type: 'hit cache', data: cachedResponse})
}
return config;
}
const setCache = (response) => {
if (!response?.config?.useCache) return response;
console.log('resp12312312', response)
if (response?.hitCache) {
console.log('response123123', response)
return response;
}
// 生成缓存key
const cacheKey = getCacheKey(response.config);
// 缓存结果
cache.set(cacheKey, response.data);
console.log('cacheKey setCache', cacheKey)
console.log('cache.keys', cache)
return response;
}
axios.interceptors.request.use(useCache);
axios.interceptors.response.use(setCache);
上述代码可以缓存请求 但是在实际场景中存在一个致命的问题。 实际场景:在main.js 中提前发起阻塞性请求,避免业务中世纪请求时阻塞页面渲染。 而预请求的时候往往请求还没结束 就开始真正的业务请求了,这时候预请求没有结束导致缓存还没写入,就会再次发起请求,反而导致了性能的浪费。 所以希望缓存的是一个promise,这个promise在请求开始时就写入缓存,并在请求结束后settled。当第二次请求时,如果缓存中有这个promise,就直接返回这个promise,而不是再次发起请求。
第二版代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import axios from 'axios';
import LRUCache from 'lru-cache';
const cache = new LRUCache({
max: 100,
maxAge: 10000
});
const useCache = config => {
if (!config?.useCache) return config;
const cacheKey = getCacheKey(config);
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
if (cachedResponse instanceof Promise) {
return cachedResponse.then(response => {
response.hitCache = true;
return response;
});
}
let source = axios.CancelToken.source();
config.cancelToken = source.token;
source.cancel({ type: 'hit cache', data: cachedResponse });
} else {
const pendingPromise = cache.get(cacheKey + ':pending');
if (pendingPromise) {
return pendingPromise;
}
const pending = axios(config).then(response => {
setCache(response);
return response;
});
cache.set(cacheKey + ':pending', pending);
return pending;
}
return config;
};
const setCache = response => {
if (!response?.config?.useCache) return response;
const cacheKey = getCacheKey(response.config);
cache.del(cacheKey + ':pending');
cache.set(cacheKey, response.data);
return response;
};
const getCacheKey = config => {
return JSON.stringify({
method: config.method,
url: config.url,
params: config.params,
data: config.data
});
};
axios.interceptors.request.use(useCache);
axios.interceptors.response.use(setCache);
第二版代码是不能运行的 这里补充一个点 我们的interceptors return的时候 是return了一个config给axios,而且在我们刚才讨论的场景中 这个请求不能被取消。我们的缓存结果也不能直接跳过实际的axios请求直接返回给调用者。所以,我们需要在axios外边包一层,来解决这个case。
第三版代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import axios from 'axios';
import LRUCache from 'lru-cache';
const cache = new LRUCache({
max: 100,
maxAge: 10000
});
const getCacheKey = config => {
return JSON.stringify({
method: config.method,
url: config.url,
params: config.params,
data: config.data
});
};
const axiosWithCache = (config, useCache = true) => {
if (!useCache) {
return axios.get(url);
}
const cacheKey = getCacheKey(config);
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
if (cachedResponse instanceof Promise) {
return cachedResponse.then(response => {
response.hitCache = true;
return response;
});
}
return Promise.resolve({
data: cachedResponse,
status: 200,
statusText: 'OK',
headers: {},
config: { ...config, useCache },
hitCache: true
});
}
const pendingPromise = cache.get(cacheKey + ':pending');
if (pendingPromise) {
return pendingPromise;
}
const axiosPromise = axios(config);
cache.set(cacheKey + ':pending', axiosPromise);
return axiosPromise.then(response => {
cache.set(cacheKey, response.data);
cache.del(cacheKey + ':pending');
response.hitCache = false;
return {
...response,
config: { ...config, useCache },
hitCache: false
};
});
};
export default axiosWithCache;
第四版代码 希望能直接替换axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import axios from 'axios';
import LRUCache from 'lru-cache';
const cache = new LRUCache({
max: 100,
maxAge: 10000
});
const getCacheKey = config => {
return JSON.stringify({
method: config.method,
url: config.url,
params: config.params,
data: config.data
});
};
const axiosWithCache = (config, useCache = true) => {
if (!useCache) {
return axios(config);
}
const cacheKey = getCacheKey(config);
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
if (cachedResponse instanceof Promise) {
return cachedResponse.then(response => {
response.hitCache = true;
return response;
});
}
return Promise.resolve({
data: cachedResponse,
status: 200,
statusText: 'OK',
headers: {},
config: { ...config, useCache },
hitCache: true
});
}
const pendingPromise = cache.get(cacheKey + ':pending');
if (pendingPromise) {
return pendingPromise;
}
const axiosPromise = axios(config);
cache.set(cacheKey + ':pending', axiosPromise);
return axiosPromise.then(response => {
cache.set(cacheKey, response.data);
cache.del(cacheKey + ':pending');
response.hitCache = false;
return {
...response,
config: { ...config, useCache },
hitCache: false
};
});
};
// 创建一个新的 Axios 实例
const axiosInstance = axios.create();
// 重写 Axios 实例的方法
axiosInstance.get = (url, config = {}) => {
return axiosWithCache({ ...config, method: 'get', url });
};
axiosInstance.post = (url, data, config = {}) => {
return axiosWithCache({ ...config, method: 'post', url, data });
};
axiosInstance.put = (url, data, config = {}) => {
return axiosWithCache({ ...config, method: 'put', url, data });
};
axiosInstance.patch = (url, data, config = {}) => {
return axiosWithCache({ ...config, method: 'patch', url, data });
};
axiosInstance.delete = (url, config = {}) => {
return axiosWithCache({ ...config, method: 'delete', url });
};
export default axiosInstance;
好的 就这样吧 累了
注:写完以后才发现虽然axios官方没有提供这个功能 但社区已经有人实现了:https://www.npmjs.com/package/axios-cache-adapter 有空看看他是怎么处理的吧