前端处理页面多次提交请求问题——防抖、节流
在实际工作中我们常会遇到需要进行内容提交的场景,常用的表单就是其中的一种。在此类过程中我们一般会向后台发送ajax请求,其中因某些原因页面短时间内多次提交同一请求是一个常会遇到的问题。尽管我们可以通过后台做一些处理来限定提交的重复内容只有一次可以生效(例如重名验证等),但是前端页面在短时间内多次提交重复请求浪费了很多性能和流量,同时如果后台没做处理也会造成一些不可预估的错误,本文就是通过一些简单的前端处理方法来解决此问题。
首先上述问题一般出现于电脑卡顿严重或网络延迟较高的时候:一般来说,为了避免出现短期重复提交,当我们提交过ajax请求后,会根据后台反馈状态来隐藏相应的提交按钮或跳转页面的,因此当电脑卡顿比较严重或网速较慢时,会出现页面没跳转过慢或者弹窗没有立即消失等情况,这时如果用户因心急或误操作多次点击提交,如果没有相应的限制,就会出现短期页面多次提交重复请求问题,所以,我们可以用如下的解决方法:
给提交按钮‘加锁’:
加锁操作十分简单,首先我们要先找到一把’锁‘,可以规定一个变量,初始值为true或者0啥的(这里用true)举例,然后在点击方法外面套一层判断,判断该变量是否为true,是的话才执行原先点击按钮要执行的内容,同时在这个方法一执行的时候,就将该变量置为false,至于’解锁‘,我们可以等后台响应,当ajax响应为我们需要的答案时,就将其改回true,以使用框架的项目中(以VUE举例),上诉操作可以这么写:
submit(){
... //表示对提交内容的前端判断处理整合等
if(this.btnStatus ===true){ //btnStatus需要在data中声明初始值,这里初始值为true
this.btnStatus = false;//这里即’锁上了'
this.$api.getCustomNameApi.submit(params).then(res => { //这里封装了统一请求,未封装的话可以改成‘axios.post(url, params).then(res=>{’或其他
if (res.result === true) {
...
this.$message.success(res.message);
this.btnStatus = true; //解锁操作
} else {
this.$message.error(res.message);
}
});
}
}
当然没用框架原生也一样,可以在js中var声明一个变量,然后通过更改其状态来达到上诉类似的效果。这种方法与独占请求十分相像,只不过独占请求使用了封装好的axios反馈状态来作为‘锁’。上述加锁解锁方式适合用于数据库插入记录如创建用户、任务等场景,不过这种将解锁放在请求正确回复时的操作,在请求回复前均不能再次发送请求,实际在应用时比较少。
常见的,我们可以设置定时器来解锁——节流的一种方法,如下:
submit(){
...
if(this.btnStatus ===true){
this.btnStatus = false;
setTimeout(function() {
this.btnStatus = true;
}, 400) //时间可以根据需要更改
this.$api.getCustomNameApi.submit(params).then(res => {
if (res.result === true) {
...
this.$message.success(res.message);
} else {
this.$message.error(res.message);
}
});
}
}
如上,可以保证我们的提交在400ms过程只有一次提交。对应到原生的js,我们也是设定一个定时器即可。这个适用于频繁点击的情形,如保存草稿的场景,但是时间间隔需设置得比较合理,这种设置通过高频事件触发,但在固定时间内只会执行一次的方法即为节流;而另一个常和节流一起提到名词——防抖则与之有区别。两者应用场景不同,防抖常用在百度搜索时输入之后都有联想词弹出这样的场景,而节流用于稀释函数的执行频率;而且两者的本质是不同的——防抖动是将多次执行变为最后一次执行,而节流是将多次执行变成每隔一段时间执行。这里有一个应用的VUE防抖示例:
export const debounce = (() => {
let timer = null
return (callback, wait) => {
clearTimeout(timer)
timer = setTimeout(callback, wait)
}
})()
// test.vue
<template>
<div class="movie-search-box">
<label for="movieName">请输入电影名字</label>
<input id="movieName" type="text" @keyup="getMovieName">
</div>
</template>
<script>
import { debounce } from "./debounce"
export default {
name: 'test',
data () {
return {}
},
methods: {
getMovieName (e) {
debounce(() => {
console.log(e.target.value)
}, 1000)
}
}
}
上诉的方法,都是只利用前端代码就可以实现的,可以过滤掉很多不必要的请求,减轻后台处理压力。当然也会有局限性,希望能对大家解决此类问题有所帮助借鉴之用。