目录
- 背景
- 思路
- 方式1-通过定时器做防抖处理
- (a)概述
- (b)代码
- (c)预览
- (d)存在的问题
- 情况-在点击事件上做防抖
- 情况-在接口方法做防抖
- 方式2-通过取消ajax请求
- (a) 概述
- (b)原理
- 原生ajax取消请求
- axios取消请求
- (c)代码
- 步骤1-通过axios请求拦截器取消重复请求
- 步骤2-通过axios响应拦截器处理请求成功
- 步骤3-通过axios响应拦截器处理请求失败
背景
项目当中前端代码会遇到同一个请求向服务器发了多次的情况,我们要避免服务器资源浪费,同一个请求一定时间只允许发一次请求
思路
(1)如果业务简单,例如同一个按钮防止多次点击,我们可以用定时器做防抖处理
(2)如果业务复杂,例如多个组件通过代码,同一个请求发多次,这个时候防抖已经不好处理了,最好是对重复的ajax请求统一做取消操作
方式1-通过定时器做防抖处理
(a)概述
效果:当用户连续点击多次同一个按钮,最后一次点击之后,过小段时间后才发起一次请求
原理:每次调用方法后都产生一个定时器,定时器结束以后再发请求,如果重复调用方法,就取消当前的定时器,创建新的定时器,等结束后再发请求,工作当中可以用第三方封装的工具函数例如lodash的debounce方法来简化防抖的代码
(b)代码
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
</head> | |
<body> | |
<div id="app"> | |
<button @click="onClick">请求</button> | |
</div> | |
</body> | |
<script> | |
// 定义请求接口 | |
function sendPost(data){ | |
return axios({ | |
url: 'https://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', | |
method: 'post', | |
data | |
}) | |
} | |
new Vue({ | |
el: '#app', | |
methods: { | |
// 调用lodash的防抖方法debounce,实现连续点击按钮多次,.3秒后调用1次接口 | |
onClick: _.debounce(async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
},), | |
}, | |
}) | |
</script> | |
</html> |
(c)预览
(d)存在的问题
无法解决多个按钮件的重复请求的发送问题,例如下面两种情况
情况-在点击事件上做防抖
按钮事件间是相互独立的,调用的是不同方法,做不到按钮间防抖效果
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
</head> | |
<body> | |
<div id="app"> | |
<button @click="onClick" ref="btn1">请求1</button> | |
<button @click="onClick" ref="btn2">请求2</button> | |
</div> | |
</body> | |
<script> | |
let sendPost = function(data){ | |
return axios({ | |
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', | |
method: 'post', | |
data | |
}) | |
} | |
new Vue({ | |
el: '#app', | |
mounted() { | |
this.$refs.btn.click() | |
this.$refs.btn.click() | |
}, | |
methods: { | |
// 使用lodash对请求方法做防抖 | |
//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 | |
onClick: _.debounce(async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
},), | |
onClick: _.debounce(async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
},), | |
}, | |
}) | |
</script> | |
</html> |
情况-在接口方法做防抖
按钮间调用的方法是相同的,是可以对方法做防抖处理,但是处理本身对方法做了一次封装,会影响到之前方法的返回值接收,需要对之前的方法做更多处理,变得更加复杂,不推荐
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/lodash.js/4.17.21/lodash.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
</head> | |
<body> | |
<div id="app"> | |
<button @click="onClick" ref="btn1">请求1</button> | |
<button @click="onClick" ref="btn2">请求2</button> | |
</div> | |
</body> | |
<script> | |
// 使用lodash对请求方法做防抖, | |
let sendPost = _.debounce(function(data){ | |
//这里有问题,这里的返回值不能作为sendPost方法执行的返回值,因为debounce内部包裹了一层 | |
return axios({ | |
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', | |
method: 'post', | |
data | |
}) | |
},) | |
new Vue({ | |
el: '#app', | |
mounted() { | |
this.$refs.btn.click() | |
this.$refs.btn.click() | |
}, | |
methods: { | |
onClick: async function(){ | |
//这里有问题,sendPost返回值不是promise,而是undefined | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res) | |
}, | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res) | |
}, | |
}, | |
}) | |
</script> | |
</html> |
方式2-通过取消ajax请求
(a) 概述
直接对请求方法做处理,通过ajax库的api方法把重复的请求给取消掉
(b)原理
原生ajax取消请求
通过调用XMLHttpRequest对象实例的abort方法把请求给取消掉
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
</head> | |
<body> | |
</body> | |
<script> | |
//原生ajax的语法 | |
let xhr = new XMLHttpRequest(); | |
xhr.open("GET", "http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test?username=zs&age=20", true); | |
xhr.onload = function(){ | |
console.log(xhr.responseText) | |
} | |
xhr.send(); | |
//在谷歌浏览器的低速g下面测试 | |
//通过XMLHttpRequest实例的abort方法取消请求 | |
setTimeout(() => xhr.abort(),); | |
</script> | |
</html> |
axios取消请求
通过axios的CancelToken对象实例cancel方法把请求给取消掉
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
</head> | |
<body> | |
</body> | |
<script> | |
/*axios的取消的语法*/ | |
// 方式-通过axios.CancelToken.source产生cancelToken和cancel方法 | |
/* | |
const source = axios.CancelToken.source(); | |
axios.get('http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', { | |
params: {username: 'zs', age:}, | |
cancelToken: source.token | |
}).then(res=>{ | |
console.log('res', res.data) | |
}).catch(err=>{ | |
console.log('err', err) | |
}) | |
//在谷歌浏览器的低速g下面测试 | |
//通过调用source的cancel方法取消 | |
setTimeout(() => source.cancel(),); | |
*/ | |
/**/ | |
// 方式-通过new axios.CancelToken产生cancelToken和cancel方法 | |
let cancelFn | |
const cancelToken = new axios.CancelToken(cancel=>{ | |
cancelFn = cancel | |
}); | |
axios.get('http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', { | |
params: {username: 'zs', age:}, | |
cancelToken: cancelToken | |
}).then(res=>{ | |
console.log('res', res.data) | |
}).catch(err=>{ | |
console.log('err', err) | |
}) | |
//在谷歌浏览器的低速g下面测试 | |
//通过调用cancelFn方法取消 | |
setTimeout(() => cancelFn(),); | |
</script> | |
</html> |
(c)代码
步骤1-通过axios请求拦截器取消重复请求
通过axios请求拦截器,在每次请求前把请求信息和请求的取消方法放到一个map对象当中,并且判断map对象当中是否已经存在该请求信息的请求,如果存在取消上传请求
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/qs/6.10.3/qs.js" type="application/javascript"></script> | |
</head> | |
<body> | |
<div id="app"> | |
<button @click="onClick" ref="btn1">请求1</button> | |
<button @click="onClick" ref="btn2">请求2</button> | |
</div> | |
</body> | |
<script> | |
//存储请求信息和取消方法的的map对象 | |
const pendingRequest = new Map(); | |
//根据请求的信息(请求方式,url,请求get/post数据),产生map的key | |
function getRequestKey(config){ | |
const { method, url, params, data } = config; | |
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&"); | |
} | |
//请求拦截器 | |
axios.interceptors.request.use( | |
function (config) { | |
//根据请求的信息(请求方式,url,请求get/post数据),产生map的key | |
let requestKey = getRequestKey(config) | |
//判断请求是否重复 | |
if(pendingRequest.has(requestKey)){ | |
//取消上次请求 | |
let cancel = pendingRequest.get(requestKey) | |
cancel() | |
//删除请求信息 | |
pendingRequest.delete(requestKey) | |
} | |
//把请求信息,添加请求到map当中 | |
// 生成取消方法 | |
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => { | |
// 把取消方法添加到map | |
if (!pendingRequest.has(requestKey)) { | |
pendingRequest.set(requestKey, cancel) | |
} | |
}) | |
return config; | |
}, | |
(error) => { | |
return Promise.reject(error); | |
} | |
); | |
let sendPost = function(data){ | |
return axios({ | |
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', | |
method: 'post', | |
data | |
}) | |
} | |
new Vue({ | |
el: '#app', | |
mounted() { | |
this.$refs.btn.click() | |
this.$refs.btn.click() | |
}, | |
methods: { | |
// 使用lodash对请求方法做防抖 | |
//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
}, | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
}, | |
}, | |
}) | |
</script> | |
</html> |
步骤2-通过axios响应拦截器处理请求成功
通过axios的响应拦截器,在请求成功后在map对象当中,删除该请求信息的数据
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/qs/6.10.3/qs.js" type="application/javascript"></script> | |
</head> | |
<body> | |
<div id="app"> | |
<button @click="onClick" ref="btn1">请求1</button> | |
<button @click="onClick" ref="btn2">请求2</button> | |
</div> | |
</body> | |
<script> | |
//存储请求信息和取消方法的的map对象 | |
const pendingRequest = new Map(); | |
//根据请求的信息(请求方式,url,请求get/post数据),产生map的key | |
function getRequestKey(config){ | |
const { method, url, params, data } = config; | |
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&"); | |
} | |
//请求拦截器 | |
axios.interceptors.request.use( | |
function (config) { | |
//根据请求的信息(请求方式,url,请求get/post数据),产生map的key | |
let requestKey = getRequestKey(config) | |
//判断请求是否重复 | |
if(pendingRequest.has(requestKey)){ | |
//取消上次请求 | |
let cancel = pendingRequest.get(requestKey) | |
cancel() | |
//删除请求信息 | |
pendingRequest.delete(requestKey) | |
} | |
//把请求信息,添加请求到map当中 | |
// 生成取消方法 | |
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => { | |
// 把取消方法添加到map | |
if (!pendingRequest.has(requestKey)) { | |
pendingRequest.set(requestKey, cancel) | |
} | |
}) | |
return config; | |
}, | |
(error) => { | |
return Promise.reject(error); | |
} | |
); | |
//响应拦截器 | |
axios.interceptors.response.use( | |
(response) => { | |
//请求成功 | |
//删除请求的信息 | |
let requestKey = getRequestKey(response.config) | |
if(pendingRequest.has(requestKey)){ | |
pendingRequest.delete(requestKey) | |
} | |
return response; | |
}, | |
(error) => { | |
return Promise.reject(error); | |
} | |
); | |
let sendPost = function(data){ | |
return axios({ | |
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', | |
method: 'post', | |
data | |
}) | |
} | |
new Vue({ | |
el: '#app', | |
mounted() { | |
this.$refs.btn.click() | |
this.$refs.btn.click() | |
}, | |
methods: { | |
// 使用lodash对请求方法做防抖 | |
//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
}, | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
}, | |
}, | |
}) | |
</script> | |
</html> |
步骤3-通过axios响应拦截器处理请求失败
通过axios的响应拦截器,在请求失败后在map对象当中,删除该请求信息的数据
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=.0"> | |
<title>Document</title> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js" type="application/javascript"></script> | |
<script src="https://lf-cdn-tos.bytecdntp.com/cdn/expire-1-M/qs/6.10.3/qs.js" type="application/javascript"></script> | |
</head> | |
<body> | |
<div id="app"> | |
<button @click="onClick" ref="btn1">请求1</button> | |
<button @click="onClick" ref="btn2">请求2</button> | |
</div> | |
</body> | |
<script> | |
//存储请求信息和取消方法的的map对象 | |
const pendingRequest = new Map(); | |
//根据请求的信息(请求方式,url,请求get/post数据),产生map的key | |
function getRequestKey(config){ | |
const { method, url, params, data } = config; | |
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&"); | |
} | |
//请求拦截器 | |
axios.interceptors.request.use( | |
function (config) { | |
//根据请求的信息(请求方式,url,请求get/post数据),产生map的key | |
let requestKey = getRequestKey(config) | |
//判断请求是否重复 | |
if(pendingRequest.has(requestKey)){ | |
//取消上次请求 | |
let cancel = pendingRequest.get(requestKey) | |
cancel() | |
//删除请求信息 | |
pendingRequest.delete(requestKey) | |
} | |
//把请求信息,添加请求到map当中 | |
// 生成取消方法 | |
config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => { | |
// 把取消方法添加到map | |
if (!pendingRequest.has(requestKey)) { | |
pendingRequest.set(requestKey, cancel) | |
} | |
}) | |
return config; | |
}, | |
(error) => { | |
return Promise.reject(error); | |
} | |
); | |
//删除请求信息 | |
function delPendingRequest(config){ | |
let requestKey = getRequestKey(config) | |
if(pendingRequest.has(requestKey)){ | |
pendingRequest.delete(requestKey) | |
} | |
} | |
//响应拦截器 | |
axios.interceptors.response.use( | |
(response) => { | |
//请求成功 | |
//删除请求的信息 | |
delPendingRequest(response.config) | |
return response; | |
}, | |
(error) => { | |
//请求失败 | |
//不是取消请求的错误 | |
if (!axios.isCancel(error)){ | |
//服务器报,500报错,删除请求信息 | |
delPendingRequest(error.config || {}) | |
} | |
return Promise.reject(error); | |
} | |
); | |
let sendPost = function(data){ | |
return axios({ | |
url: 'http://nodejs-cloud-studio-demo-bkzxs.nodejs-cloud-studio-demo..cn-hangzhou.fc.devsapp.net/test', | |
method: 'post', | |
data | |
}) | |
} | |
new Vue({ | |
el: '#app', | |
mounted() { | |
this.$refs.btn.click() | |
this.$refs.btn.click() | |
}, | |
methods: { | |
// 使用lodash对请求方法做防抖 | |
//这里有问题,只是对每个按钮的点击事件单独做了防抖,但是两个按钮之间做不到防抖的效果 | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
}, | |
onClick: async function(){ | |
let res = await sendPost({username:'zs', age:}) | |
console.log('请求的结果', res.data) | |
}, | |
}, | |
}) | |
</script> | |
</html> |