# axios封装(支持请求失败自动重发,缓存请求,设置缓存过期时长)
# 痛点:工作中总有服务器抽风的时候,比如前一秒某个接口请求失败了,后一秒接口成功,但是页面没有刷新,这时候并无法拿到最新的数据
- 缓存请求
- 关键api cancelToken ,内部通过调用
abort
方法 来实现中断请求的目的,同时调用reject让外层的promise失败。 - 请求重发
# 关于cancelToken
可以使用 CancelToken.source 工厂方法创建 cancel token,像这样
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(cancel => {
// cancel the request
cancel();
})
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 结构🌲
- request
- index.js
- requestCache.js 请求缓存
- cancelRepeatRequest.js 取消重复请求
- requestAgainSend.js 请求重发
- commonFuns.js 工具函数
1
2
3
4
5
6
7
2
3
4
5
6
7
# index
import Axios from 'axios';
import { addPendingRequest, removePendingRequest } from './cancelRepeatRquest'; // 取消重复请求
import { againRequest } from './requestAgainSend'; // 请求重发
import { requestInterceptor as cacheReqInterceptor, responseInterceptor as cacheResInterceptor } from './requestCache';
// 返回结果处理
// 自定义约定接口返回{code: xxx, result: xxx, message: 'err message'}, 根据api模拟,具体可根据业务调整
const responseHandle = {
200: response => {
return Promise.resolve(response.data);
},
201: response => {
alert(response.data.message);
console.log(`参数异常:${response.data.message}`);
return Promise.resolve(response.data);
},
404: response => {
alert('接口地址不存在');
return Promise.reject(response);
},
default: response => {
alert('操作失败');
return Promise.reject(response);
}
};
const axios = Axios.create({
baseURL: import.meta.env.BASE_URL || '',
timeout: 50000
});
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 请求头用于接口token 认证
// getToken() && (config.headers['Authorization'] = getToken());
if (config.method.toLocaleLowerCase() === 'post' || config.method.toLocaleLowerCase() === 'put') {
// 参数统一处理,请求都使用data传参
config.data = config.data.data;
} else if (config.method.toLocaleLowerCase() === 'get' || config.method.toLocaleLowerCase() === 'delete') {
// 参数统一处理
config.params = config.data;
} else {
alert('不允许的请求方法:' + config.method);
}
// pendding 中的请求,后续请求不发送(由于存放的peddingMap 的key 和参数有关,所以放在参数处理之后)
addPendingRequest(config); // 把当前请求信息添加到pendingRequest对象中
// 请求缓存
cacheReqInterceptor(config, axios);
return config;
},
function (error) {
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
response => {
// 响应正常时候就从pendingRequest对象中移除请求
removePendingRequest(response);
cacheResInterceptor(response);
return (responseHandle[response.data.code] || responseHandle['default'])(response);
},
error => {
// 从pending 列表中移除请求
removePendingRequest(error.config || {});
// 需要特殊处理请求被取消的情况
if (!Axios.isCancel(error)) {
// 请求重发
againRequest(error, axios);
}
// 请求缓存处理方式
if (Axios.isCancel(error) && error.message.data && error.message.data.config.cache) {
return Promise.resolve(error.message.data.data.result); // 返回结果数据,根据实际业务配置
}
return Promise.reject(error);
}
);
export default 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
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
# requestCache
import Axios from 'axios';
import { generateReqKey } from '../commonFuns';
const options = {
storage: true, // 是否开启loclastorage缓存
storageKey: 'apiCache',
storage_expire: 600000, // localStorage 数据存储时间10min(刷新页面判断是否清除)
expire: 20000 // 每个接口数据缓存ms 数
};
// 初始化
(function () {
let cache = window.localStorage.getItem(options.storageKey);
if (cache) {
let { storageExpire } = JSON.parse(cache);
// 未超时不做处理
if (storageExpire && getNowTime() - storageExpire < options.storage_expire) {
return;
}
}
window.localStorage.setItem(options.storageKey, JSON.stringify({ data: {}, storageExpire: getNowTime() }));
})();
function getCacheItem(key) {
let cache = window.localStorage.getItem(options.storageKey);
let { data } = JSON.parse(cache);
return (data && data[key]) || null;
}
function setCacheItem(key, value) {
let cache = window.localStorage.getItem(options.storageKey);
let { data, storageExpire } = JSON.parse(cache);
data[key] = value;
window.localStorage.setItem(options.storageKey, JSON.stringify({ data, storageExpire }));
}
let _CACHES = {};
// 使用Proxy代理
let cacheHandler = {
get: function (target, key) {
let value = target[key];
console.log(`${key} 被读取`, value);
if (options.storage && !value) {
value = getCacheItem(key);
}
return value;
},
set: function (target, key, value) {
console.log(`${key} 被设置为 ${JSON.stringify(value)}`);
target[key] = value;
if (options.storage) {
setCacheItem(key, value);
}
return true;
}
};
let CACHES = new Proxy(_CACHES, cacheHandler);
export function requestInterceptor(config, axios) {
// 开启缓存则保存请求结果和cancel 函数
if (config.cache) {
let data = CACHES[`${generateReqKey(config)}`];
// 这里用于存储是默认时间还是用户传递过来的时间
let setExpireTime;
config.setExpireTime ? (setExpireTime = config.setExpireTime) : (setExpireTime = options.expire);
// 判断缓存数据是否存在 存在的话 是否过期 没过期就返回
if (data && getNowTime() - data.expire < setExpireTime) {
config.cancelToken = new Axios.CancelToken(cancel => {
// cancel 函数的参数会作为 promise 的 error 被捕获
cancel(data);
}); // 传递结果到catch中
}
}
}
export function responseInterceptor(response) {
// 返回的code === 0 时候才会缓存下来,可根据实际业务配置
if (response && response.config.cache && response.data.code === 0) {
let data = {
expire: getNowTime(),
data: response
};
CACHES[`${generateReqKey(response.config)}`] = data;
}
}
// 获取当前时间戳
function getNowTime() {
return new Date().getTime();
}
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
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
# cancelRepeatRequest
import Axios from 'axios';
import { generateReqKey } from '../commonFuns';
// TODO: 用于把当前请求信息添加到pendingRequest对象 中;
const pendingRequest = new Map(); // Map对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
export function addPendingRequest(config) {
if (config.cancelRequest) {
const requestKey = generateReqKey(config);
if (pendingRequest.has(requestKey)) {
config.cancelToken = new Axios.CancelToken(cancel => {
// cancel 函数的参数会作为 promise 的 error 被捕获
cancel(`${config.url} 请求已取消`);
});
} else {
config.cancelToken =
config.cancelToken ||
new Axios.CancelToken(cancel => {
pendingRequest.set(requestKey, cancel);
});
}
}
}
// TODO:检查是否存在重复请求,若存在则取消已发的请求。
export function removePendingRequest(response) {
if (response && response.config && response.config.cancelRequest) {
const requestKey = generateReqKey(response.config);
// 判断是否有这个 key
if (pendingRequest.has(requestKey)) {
const cancelToken = pendingRequest.get(requestKey);
cancelToken(requestKey);
pendingRequest.delete(requestKey);
}
}
}
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
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
# requestAgainSend
import { isJsonStr } from '../commonFuns';
/**
* @param {失败信息} err
* @param {实例化的单例} axios
* @returns
*/
export function againRequest(err, axios) {
let config = err.config;
// config.retry 具体接口配置的重发次数
if (!config || !config.retry) return Promise.reject(err);
// 设置用于记录重试计数的变量 默认为0
config.__retryCount = config.__retryCount || 0;
// 判断是否超过了重试次数
if (config.__retryCount >= config.retry) {
return Promise.reject(err);
}
// 重试次数
config.__retryCount += 1;
// 延时处理
var backoff = new Promise(function (resolve) {
setTimeout(function () {
resolve();
}, config.retryDelay || 1000);
});
// 重新发起axios请求
return backoff.then(function () {
// 判断是否是JSON字符串
// TODO: 未确认config.data再重发时变为字符串的原因
if (config.data && isJsonStr(config.data)) {
config.data = JSON.parse(config.data);
}
return axios(config);
});
}
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
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
# commonFuns
import Qs from 'qs';
// generateReqKey :用于根据当前请求的信息,生成请求 Key;
export function generateReqKey(config) {
// 响应的时候,response.config 中的data 是一个JSON字符串,所以需要转换一下
if (config && config.data && isJsonStr(config.data)) {
config.data = JSON.parse(config.data);
}
const { method, url, params, data } = config; // 请求方式,参数,请求地址,
return [method, url, Qs.stringify(params), Qs.stringify(data)].join('&'); // 拼接
}
// 判断一个字符串是否为JSON字符串
export let isJsonStr = str => {
if (typeof str == 'string') {
try {
var obj = JSON.parse(str);
if (typeof obj == 'object' && obj) {
return true;
} else {
return false;
}
} catch (e) {
console.log('error:' + str + '!!!' + e);
return false;
}
}
};
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
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
# 用法(Vue3为例)
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
// 正常请求
proxy.$http.get("/api/simpleWeather/query", {
data: {
city: 481,
key: "55333d85ca99360f79d67b452b51e277"
}
});
// 主动取消请求
proxy.$http.get('/api/simpleWeather/query', {
data: {
city: 481,
key: "55333d85ca99360f79d67b452b51e277"
},
cancelRequest: true
});
// 请求重发,如果请求失败,1s后除了原请求外还会重发3次
proxy.$http.get('/api/simpleWeather/query', {
data: {
city: 481,
key: "55333d85ca99360f79d67b452b51e277"
},
retry: 3,
retryDelay: 1000
});
// 缓存请求,如果请求参数一样就不请求
proxy.$http.get('/api/simpleWeather/query', {
data: {
city: 481,
key: "55333d85ca99360f79d67b452b51e277"
},
cache: true
});
// 缓存请求:setExpireTime 为缓存有效时间ms
proxy.$http.get('/api/simpleWeather/query', {
data: {
city: 481,
key: "55333d85ca99360f79d67b452b51e277"
},
cache: true,
setExpireTime: 30000
});
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
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