竟态问题的解决方案(vue3中onInvalidate的实现思路)
问题描述
比如现在有一个需求是,页面上有一个数据列表,每当点击列表中的一项时,就会发起一个请求 A 来更新一个数据 res,再点击第二次,发起一个新的请求 B。如果请求 A 由于某些原因,延迟比较大,而 B 很快就返回来了,这就会导致 A 会在 B 之后完成并更新数据,此时 res 中的数据就是上一次点击的结果而并非正确的数据。这就是竟态问题了。
vue3给出的解决方法
首先来看 vue3 中是怎么解决这个问题的:
1 | |
watch 的第二个参数是一个回调函数 callback,而这个 callback 的第三个参数就是一个钩子函数 onInvalidate,这个钩子函数的作用就是,在下一次更新数据 obj.type 之前执行 onInvalidate 中的参数方法。因此如果出现文章开头问题描述中的问题,那么后一次的请求 B 执行之前就会调用这个钩子函数,使得 A 请求过期,这样就能保证拿到的结果是正确的。
原生js中如何实现
那么如果在 js 中应该怎么解决这个问题呢,实际上上面的思路已经有了,就是通过一个变量来控制本次请求的结果是否过期来实现的,问题就是原生 js 中没有 onInvalidate 这个钩子函数。
因此我们的思路应该是,onInvalidate 是如何实现的。在 vue3 中,watch 是通过 effect 以及 scheduler 调度函数来实现的,不过这里我们不考虑如何实现一个 watch,只考虑 onInvalidate 的实现,用它来解决我们的竟态问题。
思路重点如下:
onInvalidate是用来更新过期函数(让上一次请求的结果过期)的- 过期函数一定要在数据更新之前执行
1 | |
在上面的例子中:
- 过期函数对应的就是
clearup - 数据更新则对应的是
handleClickLiItem这个方法
可以明显的看到,过期函数是在‘数据更新’发生之前执行的。
当第一次点击按钮的时候,会先判断是否有国企函数,因为第一次进来的时候还并没有给 cleanup 赋值,自然就不会有也不会执行过期函数,然后执行了按钮的点击事件对应的回调方法 handleClickLiItem,这个方法会携带一个钩子函数 onInvalidate,在调用 onInvalidate 的时候就会更新过期函数 clearup,这个过期函数的作用就是将请求 A 的过期状态变量 isExpired 设置为过期状态 true ,之后会发送请求 A,这里我们假设 A 延迟了 10 秒。之后,在这 10 秒结束之前,我们再次点击按钮,此时因为 cleanup 已经有对应的内容了(设置 A 为过期状态),因此就会执行这个过期回调,使得 A 过期。然后再执行按钮的点击事件对应的回调方法,发送请求 B,这样就能保证即使 B 在 A 之前返回来也没有关系。