background
Concurrency conflict problem is a relatively common problem in daily development.
Different users change data in a short time interval, or repeated submission operations performed by a certain user may cause concurrency conflicts.
Concurrency scenarios are difficult to investigate comprehensively in the development and testing stages, and it is difficult to locate online bugs after online bugs. Therefore, concurrency control is a problem that needs to be paid attention to in the front-end and back-end development process.
For the problem of repeated submission of data by the same user in a short period of time, the front-end can usually do a layer of interception first.
This article will discuss how the front-end uses axios' interceptor to filter repeated requests and resolve concurrency conflicts.
General processing method-add loading every time a request is sent
Before trying the axios interceptor, let’s take a look at how our previous business dealt with concurrency conflicts:
Every time the user manipulates the controls (input boxes, buttons, etc.) on the page and sends a request to the backend, a loading effect is added to the corresponding control on the page, prompting that the data is being loaded, and it also prevents the user from continuing the operation before the loading effect ends. Control.
This is the most direct and effective way. If your front-end team members are careful and patient enough and have good coding habits, this can solve the concurrency problems caused by most users accidentally repeating submissions.
A better solution: axios interceptor unified processing
There are so many scenarios in the project that require the front-end to limit concurrency. Of course, we have to think about better and more trouble-free solutions.
Since the concurrency control is performed every time a request is sent, if the public functions of the issued request can be repackaged, and the repeated requests can be processed uniformly to achieve automatic interception, our business code can be greatly simplified.
axios
library used by the project to send http
requests, axios
officially provides us with a wealth of APIs, let’s take a look at the two core APIs needed to intercept requests:
1. interceptors
Interceptors include request interceptors and response interceptors, which can be intercepted before the request is sent or after the response. The usage is as follows:
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
2. cancel token
:
Call cancel token API
to cancel the request. The official website provides two ways to build cancel token
. We use this method: create cancel token
executor
function to the CancelToken
constructor, so that repeated requests can be executed immediately when repeated requests are detected in the above request interceptor:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// cancel the request
cancel();
The idea provided in this article is to use axios interceptors API
intercept the request, check whether there are multiple identical requests in the pending state at the same time, and if so, call cancel token API
cancel the repeated request.
If the user repeatedly clicks the button and submits two identical requests, A and B (considering the request path, method, and parameters), we can choose one of the following interception schemes:
- Cancel request A, only send request B
- Cancel B request, only issue A request
- Cancel request B, only send request A, and use the returned result of request A as the return result of request B
The third solution requires monitoring processing to increase the complexity, combined with our actual business needs, and finally adopted the second solution to achieve, namely:
only sends the first request. When the A request is still in the pending state, all subsequent requests that are repeated with A are cancelled, and only the A request is actually issued. The interception of this request is stopped until the A request ends (success/failure).
Implementation
- Store all pending requests
First, we need to store all pending status requests in the project in a variable, call it pendingRequests
,
You can axios
as a singleton class or define global variables to ensure that the pendingRequests
variable can be accessed before each request is sent, and check whether it is a repeated request.
let pendingRequests = new Map()
Combine the method, url, and parameters of each request into a string, which serves as the unique key to identify the request and also the key of the pendingRequests
const requestKey = `${config.url}/${JSON.stringify(config.params)}/${JSON.stringify(config.data)}&request_type=${config.method}`;
Little tips to help understand:
- The purpose of defining
pendingRequests
as a map object is to facilitate us to query whether it contains a certain key, and to add and delete keys. When adding a key, the corresponding value can set some user-defined function parameters, which will be used when expanding the function later. config
isaxios
interceptor, which contains the information of the current request
Check whether the current request is repeated before the request is sent
In the request interceptor, generate the above
requestKey
and checkpendingRequests
object contains the current requestrequestKey
- Yes: It means that it is a repeated request, cancel the current request
- No: add
requestKey
to thependingRequests
object
Because behind the response have to use interceptors in the current request requestKey
, in order to avoid stepping pit, it is best not to run again, at this stage put requestKey
stored back axios
interceptor config
parameters, it can be followed directly in response interceptor response.config.requestKey
it through 0609636f94e3a5.
Code example:
// 请求拦截器
axios.interceptors.request.use(
(config) => {
if (pendingRequests.has(requestKey)) {
config.cancelToken = new axios.CancelToken((cancel) => {
// cancel 函数的参数会作为 promise 的 error 被捕获
cancel(`重复的请求被主动拦截: ${requestKey}`);
});
} else {
pendingRequests.set(requestKey, config);
config.requestKey = requestKey;
}
return config;
},
(error) => {
// 这里出现错误可能是网络波动造成的,清空 pendingRequests 对象
pendingRequests.clear();
return Promise.reject(error);
}
);
pendingRequests
object after the request is returned
If the request has successfully reached the step of the response interceptor, it means that the request has ended in the pending state, then we have to remove it from pendingRequests
:
axios.interceptors.response.use((response) => {
const requestKey = response.config.requestKey;
pendingRequests.delete(requestKey);
return Promise.resolve(response);
}, (error) => {
if (axios.isCancel(error)) {
console.warn(error);
return Promise.reject(error);
}
pendingRequests.clear();
return Promise.reject(error);
})
- Need to clear the scene of the
pendingRequests
When a request error occurs due to network fluctuations or timeouts, you need to clear all previously stored request records in the pending state. The code demonstrated above has been commented.
In addition, the page also needs to be cleared before switching cache pendingRequests
object, you can use Vue Router
of beforeEach
hook:
router.beforeEach((to, from, next) => {
request.clearRequestList();
next();
});
Function extension
- Unified processing interface error prompt
Agree on the format of the data returned by the interface with the backend, and in the case of an interface error, you can add toast to the user in a unified response interceptor.
For special interfaces that do not need to report an error, you can set a parameter and store axios
in the config
parameter of the 0609636f94e666 interceptor to filter out the error message:
// 接口返回 retcode 不为 0 时需要报错,请求设置了 noError 为 true 则这个接口不报错
if (
response.data.retcode &&
!response.config.noError
) {
if (response.data.message) {
Vue.prototype.$message({
showClose: true,
message: response.data.message,
type: 'error',
});
}
return Promise.reject(response.data);
}
- Add loading effect to the control when sending the request
When using axios interceptors
filter repeated requests above, you can throw information on the console to remind the developer. On this basis, if you can add loading effects to the controls operated on the page, it will be more user-friendly.
Common ui component libraries provide loading services, and you can specify the controls that need to add loading effects on the page. The following is an example code element UI
// 给 loadingTarget 对应的控件添加 loading 效果,储存 loadingService 实例
addLoading(config) {
if (!document.querySelector(config.loadingTarget)) return;
config.loadingService = Loading.service({
target: config.loadingTarget,
});
}
// 调用 loadingService 实例的 close 方法关闭对应元素的 loading 效果
closeLoading(config) {
config.loadingService && config.loadingService.close();
}
Similar to the above filtering error method, the class name or id of the element is stored in the config
parameter of the axios
Call request interceptor addLoading
method, in response to the call interceptor closeLoading
method specified control can be achieved (e.g., Button) loading request pending during the control is automatically canceled after the loading effect of the request.
- Support the combined use of multiple interceptors
Simply look at the axios interceptors
part of the implementation source code to understand, it supports the definition of multiple interceptors
, so as long as the interceptors
defined by us conforms to the Promise.then
chain call specification, more functions can be added:
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
to sum up
Concurrency problems are very common and relatively cumbersome to deal with. When the front-end resolves concurrency conflicts, the axios
interceptor can be used to uniformly process repeated requests and simplify business code.
At the same time, the axios
interceptor supports more applications. This article provides the implementation of some commonly used extended functions. Interested students can continue to explore other uses of supplementary interceptors.
Today's content is so much, I hope it will help you.
If you find the content helpful, you can follow my official account, keep up with the latest developments, and learn together!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。