接口重复请求

finyou

防止接口重复请求

在软件开发过程中,前端需要向后端服务器发送请求,获取到响应的数据后,再将数据展示给用户。但是,在某些情况下,用户可能会多次点击某个按钮,导致多次发送相同的请求,从而浪费服务器的资源。

1.方案一:防抖和节流

1.1 防抖

1.2 节流

2.方案二:axios请求拦截器和响应拦截器

2.1 办法一

最简单的方法是在请求拦截器时,开启全局Loading(遮罩层),在响应拦截器时,关闭全局Loading。

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
import axios from "axios";
import { ElLoading } from 'element-plus'
const loadingInstance;
axios.interceptors.request.use(
function (config) {
loadingInstance = ElLoading.service({ fullscreen: true })
return config;
},
function (error) {

return Promise.reject(error);
}
);

axios.interceptors.response.use(
function (response) {
loadingInstance.close()
return response;
},
function (error) {

return Promise.reject(error);
}
);

export default axios;

2.2 办法二

如何判断是否是一个接口,可以从接口的请求方法(menthod)、请求地址(url)、请求参数(params、data)以及请求发送的页面hash,那我们是否可以通过这个信息生成一个标识key,然后key收集出来,判断集合中是否有重复的key,如果有重复的key,则表示是重复的请求。

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
import axios from "axios";


function generateReqKey(config,hash){
const {method,url,params,data} = config;
return [method,url,JSON.stringify(params),JSON.stringify(data),hash].join("&");
}

//存储已发送但未响应的请求
const pendingRequest = new Set()

axios.interceptors.request.use(
function (config) {
let hash = location.hash

//生成请求key
let reqKey = generateReqKey(config,hash);

if(pendingRequest.has(reqKey)){
return Promise.reject(new Error("重复请求"))
}else{
//添加到请求队列
config.pendKey = reqKey
pendingRequest.add(reqKey)
}

return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);

axios.interceptors.response.use(
function (response) {
pendingRequest.delete(response.config.pendKey)
return response;
},
function (error) {
pendingRequest.delete(response.config.pendKey)
return Promise.reject(error);
}
);

export default axios;

严重问题

当同一个页面中俩个组件同时发送请求时,如果请求的接口相同,会出现问题,因为俩个组件的hash值是相同的,所以俩个组件的请求key是相同的,后调接口的组件就无法拿到正确数据了。

2.3 办法三

将相同的请求先将其挂起,等到最先的请求拿到结果之后,把响应的结果进行共享

流程图

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import axios from "axios";

class EventEmitter{
constructor(){
this.events = {}
}

on(type,cbres,cbrej){
if(!this.events[type]){
this.events[type] = [[cbres,cbrej]]
}else{
this.events[type].push([cbres,cbrej])
}
}

emit(type,res,ansType){
if(!this.events[type]) return
else{
this.events[type].forEach(cbArr=>{
if(ansType === "resolve"){
cbArr[0](res)
}else{
cbArr[1](res)
}
})
}
}
}

function generateReqKey(config,hash){
const {method,url,params,data} = config;
return [method,url,JSON.stringify(params),JSON.stringify(data),hash].join("&");
}

//存储已发送但未响应的请求
const pendingRequest = new Set()

// 发布订阅容器
const ev = new EventEmitter();

axios.interceptors.request.use(
async function (config) {
let hash = location.hash

//生成请求key
let reqKey = generateReqKey(config,hash);

if(pendingRequest.has(reqKey)){
//如果相同的请求,早这里挂起,通过发布订阅来为该请求返回结果
//无论请求是否成功和失败,都需要中断请求
let res = null

try{
//接口成功响应
res = await new Promise((resolve,reject)=>{
ev.on(reqKey,resolve,reject)
})

return Promise.reject({
type:'limitResSuccess',
val:res
})
}catch(limitFunErr){
//接口报错
return Promise.reject({
type:'limitResError',
val:res
})
}

}else{
//添加到请求队列
config.pendKey = reqKey
pendingRequest.add(reqKey)
}


return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);

axios.interceptors.response.use(
function (response) {
handleSuccessResponse_limit(response)
return response;
},
function (error) {

return handleErrorResponse_limit(error)
}
);


//接口响应成功
function handleSuccessResponse_limit(response){
const reqKey = response.config.pendKey
if(pendingRequest.has(reqKey)){
let x = null
try{
x = JSON.parse(JSON.stringify(response))
}catch(err){
x = response
}

pendingRequest.delete(reqKey)
ev.emit(reqKey,x,'resolve')
delete ev.reqKey
}
}

//接口失败响应
function handleErrorResponse_limit(error){
if(error.type === "limitResSuccess"){
return Promise.resolve(error.val)
}else if(error.type === "limitResError"){
return Promise.reject(error.val)
}else{
const reqKey = error.config.pendKey
if(pendingRequest.has(reqKey)){
let x = null
try{
x = JSON.parse(JSON.stringify(error))
}catch(err){
x = error
}
pendingRequest.delete(reqKey)
ev.emit(reqKey,x,'reject')
delete ev.reqKey
}
}

return Promise.reject(error)
}
export default axios;