利用 Promise.race 提前终止多余异步请求

在开发中复杂列表或图表,加上各种条件、筛选是个常见的需求,接口响应速度通常比较慢。

一般筛选条件触发刷新的操作都会加上防抖,避免短时间多次重复请求,极端情况下防抖并非万无一失的策略。我们来模拟一下:

防抖等待时间为300毫秒,用户第一次改变条件,第一个请求正常发起,较为耗时需要1秒;300毫秒时用户第二次改变条件,没有触发防抖机制,正常发起请求,耗时较短200毫秒响应结果,此时页面上显示为第二次改变的条件和第二次请求的数据,正常;1秒后第一次请求完成响应,此时页面上图表数据为第一次请求的内容,但是条件扔为第二次的,异常。

理想情况下,当第二次请求发起后第一次请求结果将不渲染。

如何实现呢?大部分项目中的axios或其他请求工具,都被promise包装,无法用原生的方法将请求提前终止。

Promise.race 接受一个promise数组作为参数,包装一个新的promise实例,新的promise实例将返回数组中最快结束的结果。我们可以利用Promise.race的竞速机制防止promise进入then回调,变相实现提前终止异步请求。实现思路:

将正常的 promise 和一个“假” promise 用 Promise.race 包装,并利用闭包保存 “假” promise 中的 reject 方法。调用reject后,整个promise进入失败阶段,被终止。

代码:


/**
 *  解决异步操作冲突的问题
 *  多次触发后,丢弃未响应的结果,只有最新的一次会进入 then回调中
 *  @template T
 *  @param {T & function} fn 需要包装的异步操作
 *  @return T
 */
export function useLatestCall(fn) {
  //  “假” promise 中的 reject 方法
  let prePromiseReject
  return function() {
    // 如果存在,就行终止
    prePromiseReject?.(new Error('新的promise执行,旧promise被丢弃'))
    // 创建“假” promise,并将reject赋值给闭包变量
    const fakePromise = new Promise((resolve, reject) => (prePromiseReject = reject))
    return Promise.race([fakePromise, fn(...arguments)])
    	.finally(() => (prePromiseReject = null))
  }
}

业务中使用(vue 或者 react中具体写法不同,大致如此):

const fetchData = useLatestCall(getXXXXX)
const reloadPage = () => {
  // 只有最近触发的一次会进入 then 回调
  fetchData(params).then(res => {
    // xxx
  })
}
posted @ 2021-10-09 08:57  叫我阿森就行  阅读(561)  评论(0)    收藏  举报