1、文章开篇:什么是幂等?
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。
简单说:在编程中就是发多次请求,但是请求都是一样的,结果是一样的。
比如说,向数据库中新增1条数据,其实简单的来说就是设一个点击按钮,这个都没有问题,但是假如说我们向后端传输的数据有很多,页面并没有说及时得到反映,这个时候我们应该如何去处理?
其实很多前端程序员的通病就是写代码的时候不考虑代码的复用以及组件的扩展性,包括我个人也是一样,这样就会导致后期要维护的时候Bug有很多,同样代码的耦合性也会同样很高,当然这样肯定是不好的。
好的言归正传说会 幂等这个问题,如果是增加一条数据,那么前端页面肯定是会去触发一个事件,但是如果我们手抖不小心点击了两次呢?是不是就会向后端请求两次数据呢?
答案当然会,其实这就是幂等问题,包括像删除,假如说我要根据一条数据的id去删除数据,我第一次点击按钮请求时,那么数据库后端已经根据这个id去对数据库中的数据进行了操作,但是如果我在请求的过程中又点击了一次,其实这个时候如果后端再去根据这个id去更改变更数据库。仔细考虑一下,会出现什么问题呢?
当然言归正传,后端自然会有后端的处理方案,悲观锁,乐观锁,本地锁等等。
当然今天我们主要讲的就是前端如何去处理,尽量去减少后端的幂等?
1、前端怎么样去做处理?
看到这个转圈圈是不是很熟悉?
没错就是新增一个loading效果,每次当我们向后端发送请求时,新增一个转圈圈效果那不就好了么?
在这里的话我们就不讲js原生了,因为其实loading的原理就是创建一个隐藏的遮罩层,当用户点击button的时候,就把这个遮罩层展示出来,当数据已经从服务器返还回来时,就让这个遮罩层隐藏。
当然在现在基本上我们都是用ui框架的年代里,loading肯定是各个ui库都是已经给我们封装好了的,那么我们今天就来详细的介绍一下关于element的loading加载效果
2、效果
首先我们可以看一下官网给我们的案例
<template>
<el-button
type="primary"
@click="openFullScreen1"
v-loading.fullscreen.lock="fullscreenLoading">
指令方式
</el-button>
<el-button
type="primary"
@click="openFullScreen2">
服务方式
</el-button>
</template>
<script>
export default {
data() {
return {
fullscreenLoading: false
}
},
methods: {
openFullScreen1() {
// 当点击了指令方式时就会触发 loading加载效果 指令方式是使用v-loading方式指令这种形式
this.fullscreenLoading = true;
setTimeout(() => {
// 2s之后关闭loading加载效果
this.fullscreenLoading = false;
}, 2000);
},
openFullScreen2() {
// 当点击了 服务方式时 首先触发一个加载效果,loading中的参数对象就是配置项,让其显示何种样式的遮罩层
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
setTimeout(() => {
// 2s后关闭对应的遮罩层。
loading.close();
}, 2000);
}
}
}
</script>
从逻辑上来说,我们前端每次在点击按钮之前,就设置上遮罩,当数据返还回来时就直接给这个遮罩层关了.
但是我们需要考虑的问题就是,如果我们这样去做,请注意,我们给每一个请求去添加loading效果,首先本身这就是一件非常费力的事情,当然如果产品有需求,要求严格指定什么需要添加loading效果的话,那么就必须去单独的去加了,不过最好还是封装一个全局的方法吗,然后去调用一下,这样肯定是比较好的,这个时候我们就可以在app.vue里使用provide、inject 这种方式来传递this调用这个父根方法,当然也可以main入口文件定义在prototype上,挂载在vue的原型对象上,到时候用的时候直接去调用即可.
3、配合axios,请求拦截器,相应拦截器使用loading效果。
在需求没有那么严格的情况下,首先我们肯定是有简单,高效的方法来解决多次点击按钮这个问题的,那么既然我们使用的是vue框架的话(当然其他框架像react也是同样一个道理,配置axios即可),我们就可以直接在axios的请求拦截器、响应拦截器中实现loading这个加载的效果。
首先我们来看axios的官网给出我们拦截器的api
拦截器
在请求或响应被 then 或 catch 处理前拦截它们。
// 添加请求拦截器
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);
});
我们就可以根据拦截器来做一系列的事件
直接上代码
import axios from 'axios'
import router from '@/router'
import qs from 'qs'
import { Loading } from 'element-ui'; // 记住按需导入
// 因为我在这边是使用的自定义的axios 性质是一样的,定义了一个全局变量,根据此全局变量来跟进
const service = axios.create({
baseURL: '',
timeout: 30000,
withCredentials: true
})
// 请求拦截器
let loadingInstance = ''
service.interceptors.request.use(config => {
// 增添token
config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
loadingInstance = Loading.service({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
return config
}, err => {
Promise.reject(err)
})
// 响应拦截器
service.interceptors.response.use(response => {
// 以服务的方式调用的 Loading 需要异步关闭
loadingInstance.close();
// 如果正常直接返回对应data请求数据
return response.data
}, err => {
return Promise.reject(err)
})
此样即实现了全局效果,但是如果说真的就一点问题都没有了吗?
那肯定不是的,注意,一旦当我们返还了数据之后对应的loading加载效果就已经清空,但是如果一个页面加载的时候发送传递了多条请求呢?项目中一个页面是不可能只发送一个请求的。
我们上方,但凡只要有数据返还那么就会去除loading效果,那如果其他的数据没有返还回来呢?loading是不是还应该在一个加载的过程中?
4、改写、完善Loading加载效果
// 请求拦截器
let requestNum = 0
let loadingInstance = ''
service.interceptors.request.use(config => {
// 每次请求时requestNum++
requestNum++
config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
loadingInstance = Loading.service({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
return config
}, err => {
// 如果请求一旦出现问题 requestNum = 0 loadingInstance.close();
requestNum = 0
loadingInstance.close();
Promise.reject(err)
})
// 响应拦截器
service.interceptors.response.use(response => {
// 每次请求时-- 主要时用于判断当前共请求了多少次,如果返还的次数不等于请求的次数的话就不会进入这个条件
// 只有当所有数据全返还时,才会关闭loading效果
requestNum--
if (requestNum <= 0) {
loadingInstance.close();
requestNum = 0
}
// 如果正常直接返回对应data请求数据
return response.data
}, err => {
// 如果请求一旦出现问题 requestNum = 0 loadingInstance.close();
requestNum = 0
loadingInstance.close();
return Promise.reject(err)
})
这样当所有页面全部返还之后才会将loading加载效果关闭