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

浙公网安备 33010602011771号