写在前面
使用axios怎样在连续请求同一个接口时,取消前面的请求,通俗一点来讲就是对接口请求做了个“防抖”的操作。场景:比如现在页面上有个列表查询的按钮,当用户在一秒钟之内,多次点击这个按钮时,那么接口此接口请求会发生多次,前一个接口请求结果还未返回就进行了下一次请求,这样做不但会造成资源的浪费,同时还会加重服务端的压力,怎样避免这样的操作呢?此时,我们只需要在下一个接口发出时,取消前面这个一模一样的接口的请求即可。
幸运的是axios给我们提供了这样的一个骚操作:cancelToken。
话不多说,直接上code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import axios from 'axios'
const http = axios.create()
const Cancel = axios.CancelToken
let httpPending = []
function removeHttpPending(config) { httpPending.map((item, index, arr) => { if (item.u === config.url + '&' + config.method) { console.warn(`${config.url}: 短时间内重复请求`) item.f() arr.splice(index, 1) } return config }) }
function clearHttpPending() { httpPending = [] }
http.interceptors.request.use((config) => { removeHttpPending(config) config.cancelToken = new Cancel(c => {
httpPending.push({ u: config.url + '&' + config.method, f: c }) })
return config }, (err) => { return Promise.reject(err) })
http.interceptors.response.use((res) => { clearHttpPending()
return res.data }, (err) => { console.error(err) return Promise.reject(err) })
|
大致思路是在请求拦截器中,把请求缓存在一个数组中,然后在每次请求的时候,对数组进行检索,如果存在未完成的请求,则执行取消请求的操作,注意这里取消的是上一个还未完成的请求,并不是此次请求。那么怎样取消请求呢,请看这一段代码:
1 2 3 4
| config.cancelToken = new Cancel(c => {
httpPending.push({ u: config.url + '&' + config.method, f: c }) })
|
这段代码在请求拦截器中对此次请求进行了储存,config.cancelToken等于new了一个Cancel类的实例,其中的参数为一个回调函数,回调函数的参数即是可以取消此次请求的函数,执行这个函数即可取消此次请求操作。需要说明的是,取消请求操作,是不会有数据返回的,也就是不会走到响应拦截器中的代码,如果走到了响应拦截器,那就说明此次接口请求之后,短时间内没有紧接着的密集的同类请求,所以,我们需要在响应拦截器中对储存的接口缓存数组进行清空操作,这也就是函数clearHttpPending的存在的原因。
另外,再来说一说最重要的函数removeHttpPending,即取消前一次请求的函数。先来看在请求拦截器中的cancelToken的时候进行储存的对象:
1 2 3 4
| config.cancelToken = new Cancel(c => {
httpPending.push({ u: config.url + '&' + config.method, f: c }) })
|
数组httpPending进行push的这个对象{ u: config.url + ‘&’ + config.method, f: c },这个对象是我们自己定义的,u代表此次请求的标识,也就是你在后面执行取消操作的时候,怎么知道你上一个请求和此次请求是属于对同一个接口发起的一模一样的请求,这里我们采用的是请求的路径url和方法method,键f存的是取消请求的函数c。
所以,在函数removeHttpPending中,我们对config的url和method拼接起来的字符串和数组中已经储存的对象的键u进行比较,如果相同,则说明上一个请求和此次请求是属于同一个请求,如果满足这个判断,则需要进行取消请求的操作,即要执行对象中已经储存的键f的值c函数。所以,函数removeHttpPending就变成了这样:
1 2 3 4 5 6 7 8 9 10 11
| function removeHttpPending(config) {
httpPending.map((item, index, arr) => { if (item.u === config.url + '&' + config.method) { console.warn(`${config.url}: 短时间内重复请求`) item.f() arr.splice(index, 1) } return config }) }
|
如果要更严谨一些,那么在函数removeHttpPending还要对请求参数进行验证,如满足了url和method相同外,还要满足请求参数也是一样的,才取消请求。如此,我们可以这样改造函数removeHttpPending,请看如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| function removeHttpPending(config) {
httpPending.map((item, index, arr) => {
if (item.u === config.url + '&' + config.method) { if (!(item.data && isSameKeyValueObj(item.data, config.data)) && !(item.params && isSameKeyValueObj(item.params, config.params))) { return config } console.warn(`${config.url}: 短时间内重复请求`) item.f() arr.splice(index, 1) } return config }) }
function isObj(obj) { const str = Object.prototype.toString.call(obj) if (str === '[object Object]' || str === '[object Array]') { return true } return false }
function isSameKeyValueObj(target, source) { if (!isObj(target) || !isObj(source)) { return false } const targetkeys = Object.getOwnPropertyNames(target) const sourcekeys = Object.getOwnPropertyNames(source) if (targetkeys.length !== sourcekeys.length) { return false } for (let i = 0; i < targetkeys.length; i++) { const key = targetkeys[i] if (!isObj(target[key]) && target[key] !== source[key]) { return false } if (isObj(target[key])) { return isSameObj(target[key], source[key]) } } return true }
|
相应的请求拦截器中的代码变成了如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| http.interceptors.request.use((config) => { removeHttpPending(config) config.cancelToken = new Cancel(c => {
httpPending.push({ u: config.url + '&' + config.method, f: c, data: config.data, params: config.params }) }) return config }, (err) => { return Promise.reject(err) })
|
如此,我们就完成了对请求更加严谨的判断,即除了对url和方法method进行对比外,还加入了data和params的对比。