重复请求——取消上次请求

在某些场景下,同个接口重复请求时(参数不同),如果第一次请求的耗时比第二次请求耗时长,那么会导致页面最终渲染的是第一次请求的结果,由此产生错误的显示。

可能会出现此问题的场景

1. 列表筛选

在这里插入图片描述

“列表筛选”的场景也可以采用加个锁的形式来处理,每选一次条件就显示loading,杜绝了重复请求的可能性。但是对于手快的用户来说并不友好,操作不够流畅,可酌情使用。

2. 搜索建议

在这里插入图片描述

“搜索建议”这种场景就不可能加锁了,限制输入频率用户绝对会炸毛的。


通常这类场景:

  1. 不能采用取消后续发起请求的方式来处理,因为每一次的请求都是获取不一样的结果。
  2. 不能采用防抖/节流的方式来处理,因为很难把控间隔时间,用户的网络环境是不确定的,如果响应时间长仍然会发生重复发起请求的情况,防抖需要设置延时,实际上也导致接收响应的时间延后,影响用户体验。

这类场景的频繁请求同个接口是不可避免的,所以解决方案的目标应该是:每当发起请求时,如果该接口有旧请求还在等待响应,那么取消旧请求。

(PS:已发出的请求,服务端是能够接收并响应的,这里的取消请求准确来说应该是取消处理请求结果。)

先准备一个能复现问题的小案例

为了能验证后面提到的解决方案,我利用了koa搭建了一个服务,并写了个简单的页面。文末我会附上该案例地址,大家也可以尝试一下嗷。

界面是长这样的:

在这里插入图片描述

这里我给三个选项的响应分别设置了延时——全部:1000毫秒类型1:500毫秒类型2:0毫秒

依次快速点击类型1类型2,最终显示的结果是耗时较长的类型1:

在这里插入图片描述

方案一:借助 axios 的 CancelToken

如果项目中用的是 axios,那就可以利用 axios 提供的 cancel token API 来取消请求。为了方便使用,封装成了如下函数:

import axios from 'axios'
/**
 * 限制重复请求
 * @param {function} callback - 返回 Promise 实例的函数
 * @returns {function} - 新函数
 */
function lastPromise(callback) {
  const CancelToken = axios.CancelToken
  let source = null
  return function(...args) {
    // 如果已存在实例则取消,并触发新的请求
    if (source) {
      source.cancel()
    }
    source = CancelToken.source()
    return callback(...args, source).then((res) => {
      source = null
      return res
    })
  }
}
    

使用示例:

const getContent = lastPromise((params, source) => {
  return axios({
    url: '/content',
    params,
    cancelToken: source.token
  })
})

依次快速点击全部类型1类型2,效果如下:

因为cancel token API内部调用了 XMLHttpRequest.abort() 方法,所以很直观地看到前面两个请求已经是取消状态了。

在这里插入图片描述

方案二:利用 Promise.race 抢跑

如今异步请求基本都利用 Promise 来接收响应,那么就可以利用 Promise.race 的特性,率先改变一个临时 Promise 的状态为rejected,跳过后续 then 方法指定回调的执行。

/**
 * 限制重复请求
 * @param {function} callback - 返回 Promise 实例的函数
 * @returns {function} - 新函数
 */
function lastPromise(callback) {
  let cancelPromise = null
  let cancelToken = null
  return function(...args) {
    // 如果已存在实例则取消,并触发新的请求
    if (cancelToken) {
      cancelToken('cancelPromise')
    }
    cancelPromise = new Promise((resolve, reject) => {
      cancelToken = reject
    })
    return Promise.race([cancelPromise, callback(...args).finally(() => {
      cancelPromise = null
      cancelToken = null
    })])
  }
}

发起请求后的执行流程:

yes
yes
Promise.race
cancelPromise
cancelPromise先改变状态?
catch
callback
finally
callback先改变状态?
then/catch

使用示例:

const getContent = lastPromise(params => {
  return axios({
    url: '/content',
    params
  })
})

依次快速点击全部类型1类型2,效果如下:

尽管在开发者工具中看到前面两个请求是正常完成的状态,但因为它们的 Promise.race 中是 cancelPromise 先改变状态为 rejected,因此前面两个请求都是直接跳到 catch 方法指定回调的执行(如果有写 catch 的话),这样就避免了前面比较耗时的请求结果会覆盖新请求的结果。

在这里插入图片描述

- END -


案例克隆地址:git clone https://gitee.com/huang_kai_da/lastPromise.git (启动说明在 README.md)

感谢阅读,如果有疑问或发现错误,欢迎在评论区留言讨论哦,与大家共同进步~
如果感觉本文能给到你一点点帮助,欢迎点赞、收藏加关注支持博主~

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用axios提供的取消请求的功能来取消上次请求并发送当前请求。首先,你需要创建一个取消令牌(cancel token),然后将其传递给你的请求。当你想要取消请求时,只需调用取消令牌的cancel方法。 以下是一个示例代码: ```javascript import axios from 'axios'; // 创建取消令牌 const cancelTokenSource = axios.CancelToken.source(); // 发送请求 axios.get('https://api.example.com/data', { cancelToken: cancelTokenSource.token }) .then(response => { // 处理响应数据 console.log(response.data); }) .catch(error => { if (axios.isCancel(error)) { // 请求取消 console.log('请求取消', error.message); } else { // 处理其他错误 console.log('请求发生错误', error.message); } }); // 取消上次请求并发送当前请求 cancelTokenSource.cancel('取消上次请求'); // 发送当前请求 axios.get('https://api.example.com/new-data', { cancelToken: cancelTokenSource.token }) .then(response => { // 处理响应数据 console.log(response.data); }) .catch(error => { if (axios.isCancel(error)) { // 请求取消 console.log('请求取消', error.message); } else { // 处理其他错误 console.log('请求发生错误', error.message); } }); ``` 在这个示例中,我们首先创建了一个取消令牌cancelTokenSource,并将其传递给第一个请求。然后,我们调用cancelTokenSource的cancel方法来取消上次请求,并发送当前请求。在请求的then和catch方法中,我们可以根据axios.isCancel方法来判断请求是否被取消,并进行相应的处理。 希望这个示例对你有帮助!如果你有任何其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值