深色模式
前端控制重复请求
背景
假设页面上有一个按钮,连续3次点击该按钮,向后端发起3次请求,记作 req1、req2、和 req3, 返回结果记作 res1、res2、和 res3。请求返回可能按照请求顺序返回,也可能不按照请求顺序返回。如果 res1, 和 res2 处在 pending 状态時,res3已经返回,则此时我们需要res3数据,res1和res2再返回,则是脏数据。如果我们需要的返回值是最新的一次请求返回值。如何处理呢。
请求队列
设计一个请求队列,只有上一次的请求返回,才执行下一个请求任务。
该方案可以保证请求结果的正确性,但是是同步处理,效率慢了些。
节流 throttle
前端按照一定的频率请求后端接口。节流是在指定时间内只执行一次操作。
节流适用在 resize
, scroll
, mousedown
等操作。
节流会有漏掉请求的操作。
参考 节流
防抖 debounce
防抖是指定时间内只执行最后一次任务。同样也会有漏掉请求的可能。
防抖适用于如前端搜索请求操作。
缓存请求token
将请求 key 缓存,每次请求更新最新的 token,某个请求返回验证 token是否相同,则数据是最新处理结果。
js
const cache = {};
function isValidateAsync(key) {
const ts = Date.now();
cache[key] = ts;
return () => cache[key] === ts;
}
function fn(params = {}) {
const key = JSON.stringify(params);
const isValidateFn = isValidateAsync(key);
asyncFn(params).then((res) => {
if (isValidateFn()) {
// 正确处理
}
});
}
XMLHttpRequest.abort()
xhr 请求可以中止
js
const xhr = new XMLHttpRequest()
xhr.open('http://127.0.0.1:4000/asyncreq')
xhr.send()
// 取消请求
xhr.abort()
Axios 使用 AbortController
前端代码
js
const axios = require('axios');
function getReqKey(config = {}) {
const { method, url, params, data } = config;
const reqKeyObj = {
method,
url,
params,
data,
};
return JSON.stringify(reqKeyObj);
}
const abortControllerMap = new Map();
function addPendingAbortController(config = {}) {
const reqKey = getReqKey(config);
let controller = abortControllerMap.get(reqKey);
if (!controller) {
controller = new AbortController();
config.signal = controller.signal;
abortControllerMap.set(reqKey, controller);
}
return controller;
}
function removePendingController(config = {}, isSelf = false) {
const reqKey = getReqKey(config);
const controller = abortControllerMap.get(reqKey);
if (!isSelf && controller) {
controller.abort();
}
abortControllerMap.delete(reqKey);
}
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
removePendingController(config);
addPendingAbortController(config);
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
removePendingController(response.config, true);
return response;
},
function (error) {
removePendingController(error.config || {}, true);
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
}
);
function onRequest(index = 1, waitTime = 0) {
axios
.get(`http://localhost:4000/asyncreq?waitTime=${waitTime}`)
.then((res) => {
console.log(`第${index}次请求结果`, res.data);
})
.catch((error) => {
if (axios.isCancel(error)) {
console.log(`第${index}次请求取消`, error.code);
} else {
console.log(`第${index}次请求出错`, error.code);
}
});
}
onRequest(1, 4000);
onRequest(2, 4000);
onRequest(3, 4000);
后端代码
js
router.get('/asyncreq', async ctx => {
const waitTime = Math.floor(Math.random() * 3000) + 1000;
await delayTime(waitTime);
ctx.body = ctx.request.query;
});
测试结果
shell
第1次请求取消 ERR_CANCELED
第2次请求取消 ERR_CANCELED
第3次请求结果 { waitTime: '4000' }