axios连续请求同一个接口的时候取消前一个请求

8 篇文章 0 订阅
1 篇文章 0 订阅

我的最新博客地址:我的最新博客

使用axios怎样在连续请求同一个接口时,取消前面的请求,通俗一点来讲就是对接口请求做了个“防抖”的操作。场景:比如现在页面上有个列表查询的按钮,当用户在一秒钟之内,多次点击这个按钮时,那么接口此接口请求会发生多次,前一个接口请求结果还未返回就进行了下一次请求,这样做不但会造成资源的浪费,同时还会加重服务端的压力,怎样避免这样的操作呢?此时,我们只需要在下一个接口发出时,取消前面这个一模一样的接口的请求即可。

幸运的是axios给我们提供了这样的一个骚操作:cancelToken。

话不多说,直接上code:

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)
})

大致思路是在请求拦截器中,把请求缓存在一个数组中,然后在每次请求的时候,对数组进行检索,如果存在未完成的请求,则执行取消请求的操作,注意这里取消的是上一个还未完成的请求,并不是此次请求。那么怎样取消请求呢,请看这一段代码:

config.cancelToken = new Cancel(c => {

    httpPending.push({ u: config.url + '&' + config.method, f: c })
  })

这段代码在请求拦截器中对此次请求进行了储存,config.cancelToken等于new了一个Cancel类的实例,其中的参数为一个回调函数,回调函数的参数即是可以取消此次请求的函数,执行这个函数即可取消此次请求操作。需要说明的是,取消请求操作,是不会有数据返回的,也就是不会走到响应拦截器中的代码,如果走到了响应拦截器,那就说明此次接口请求之后,短时间内没有紧接着的密集的同类请求,所以,我们需要在响应拦截器中对储存的接口缓存数组进行清空操作,这也就是函数clearHttpPending的存在的原因。

另外,再来说一说最重要的函数removeHttpPending,即取消前一次请求的函数。先来看在请求拦截器中的cancelToken的时候进行储存的对象:

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就变成了这样:

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,请看如下代码:

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
}

相应的请求拦截器中的代码变成了如下:

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的对比。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要封装一个全局请求接口,可以使用 axios 的拦截器来实现。拦截器可以在请求发送或响应返回后对请求或响应进行处理,可以用来统一处理请求头、响应拦截、错误处理等操作。下面是一个简单的 axios 封装示例: ```javascript // 导入 axios import axios from 'axios' // 创建 axios 实例 const instance = axios.create({ baseURL: 'http://api.example.com', // 设置请求的基础 URL timeout: 5000 // 设置请求间 }) // 请求拦截器 instance.interceptors.request.use( config => { // 在请求发送之做些什么 // 统一添加请求头信息 config.headers.Authorization = localStorage.getItem('token') return config }, error => { // 请求错误做些什么 return Promise.reject(error) } ) // 响应拦截器 instance.interceptors.response.use( response => { // 对响应数据做些什么 return response.data }, error => { // 对响应错误做些什么 if (error.response) { // 根据响应状态码进行错误处理 switch (error.response.status) { case 401: // 未登录,跳转到登录页 break case 403: // 没有权限,提示用户 break case 404: // 请求的资源不存在,提示用户 break default: // 其他错误,提示用户 } } return Promise.reject(error) } ) // 导出封装后的 axios 实例 export default instance ``` 这样就可以在项目中使用封装后的 axios 实例,而不用每次都手动配置请求头、错误处理等操作。例如: ```javascript import axios from '@/utils/request' // 发送 GET 请求 axios.get('/users').then(response => { console.log(response) }) // 发送 POST 请求 axios.post('/login', { username: 'admin', password: '123456' }).then(response => { console.log(response) }) ``` 这里的 `/users` 和 `/login` 是相对于 `baseURL` 的路径,例如 `http://api.example.com/users`、`http://api.example.com/login`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值