请求超时后如何优雅的重新请求
在日常工作中经常会看到请求超时这个概念,有时候网络慢或者其他异常原因导致一个请求迟迟得不到响应,我们就需要对这一异常情况做处理。通常的做法就是设置一个超时时间,一旦超过了这个时间还没有响应就视为超时并且取消掉这个请求然后捕获这个错误。 那么在不借助第三方库的情况下我们该如何自己实现一个请求超时并重新发起请求呢?
- 设计逻辑
答
这里我想要实现一个请求超时了就取消当前超时的请求,并且重新发起一个请求,最多重复3次。如果3次都超时则返回请求失败,下面就来一步一步去实现一下这个工具函数
- 首先我们实现一个最基础的请求函数
/**
* 发送fetch请求
* @param url 请求路径
* @param config fetch配置
*/
const sendTimedRequest = (url,config)=>{
return new Promise((resolve, reject)=>{
fetch(url,{
...config,
}).then(response=>{
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
const error = new Error(`response error.`);
error.response = response;
reject(error);
}
}).catch(err=>{
reject(err)
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
这是一个最基础发送请求的函数
- 下面我们来实现对请求超时的处理
const DEFAULT_TIMEOUT = 1000 * 10; //设置10s的默认超时时间
const sendTimedRequest = (url,config)=>{
// 超时时间
const _fetchTimeout = config.timeout||DEFAULT_TIMEOUT
return new Promise((resolve, reject)=>{
// 发送请求的函数
function sendRquest(){
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal; // 用来取消fetch请求
// 超时的定时器
const timer = setTimeout(()=>{
controller.abort(); // 取消请求
sendRquest() // 再次发起请求
},_fetchTimeout)
fetch(url,{
...config.fetchConfig,
signal
}).then(response=>{
clearTimeout(timer) // 如果在超时之前返回了就可以清楚定时器了
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
const error = new Error(`response error.`);
error.response = response;
reject(error);
}
}).catch(err=>{
if(err.name==='AbortError'){
// 处理请求取消的情况
console.log('Fetch aborted');
}else{
// 处理其他错误
console.error('Fetch error:', err);
// 如果请求失败了 则直接关闭超时请求的逻辑
clearTimeout(timer)
reject(err)
}
})
}
sendRquest()
})
}
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
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
这里引入了一个判定超时的定时器,定时器的回调里我们会取消掉当前请求,并且重新发起一个请求,重复这个逻辑。需要注意的是取消请求也会被fetch
的catch
捕获到,所以我们需要在catch
里
进行一个判断,如果是捕获到的是取消请求则不改变promise
状态,视为整个过程还未完成,因为我们会去重新发起请求。
- 下面我们加入重试次数限制
const DEFAULT_TIMEOUT = 1000 * 10; //设置10s的默认超时时间
const DEFAULT_RETRY_TIMES = 1000 * 10; //设置3次的默认重试次数
const sendTimedRequest = (url,config)=>{
// 超时时间
const _fetchTimeout = config.timeout||DEFAULT_TIMEOUT
// 重试次数
const retryTimes = config.retryTimes||DEFAULT_RETRY_TIMES
return new Promise((resolve, reject)=>{
// 发送请求的函数
function sendRquest(){
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal; // 用来取消fetch请求
// 超时的定时器
const timer = setTimeout(()=>{
controller.abort(); // 取消请求
// 如果重试次数大于0则重新发起请求
if(retryTimes>0){
sendRquest() // 再次发起请求
retryTimes--
}
},_fetchTimeout)
fetch(url,{
...config.fetchConfig,
signal
}).then(response=>{
clearTimeout(timer) // 如果在超时之前返回了就可以清楚定时器了
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
const error = new Error(`response error.`);
error.response = response;
reject(error);
}
}).catch(err=>{
if(err.name==='AbortError'){
// 处理请求取消的情况
console.log('Fetch aborted');
if(retryTimes===0){
reject(err)
}
}else{
// 处理其他错误
console.error('Fetch error:', err);
// 如果请求失败了 则直接关闭超时请求的逻辑
clearTimeout(timer)
reject(err)
}
})
}
sendRquest()
})
}
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
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
这里多引入了一个retryTimes
字段表示重复请求的次数,在超时的定时器里会去判断当前是否还有可重复执行的次数,没有的话就不会再去发起请求了,并且还是会取消掉当前超时的请求。
这里需要注意的是:在上一步中我们在fetch
的catch
里捕获到取消请求的错误是不会去改变promise
状态的,而这里我们需要再加一个判断,如果捕获到了取消请求的错误,并且retryTimes
为0
也就是说请求超时了,并且所有重试次数都耗尽了,那就视为这个请求失败了,可以reject
掉这个promise
了。
整体的实现过程并不复杂,主要是借助AbortController
控制器来取消超时的请求
编辑 (opens new window)
上次更新: 2025/01/17, 17:02:07