Vue Composable 取消语义:让旧请求真的停止影响界面

Vue Composable 取消语义:让旧请求真的停止影响界面原创封面

先区分停止关注与真正取消工作

组合式函数在参数变化或组件卸载时,最容易犯的错误是只忽略旧结果,却让旧请求、计时器和流继续运行。忽略结果能够避免一次界面回退,但不能释放连接、服务器计算和浏览器内存。设计取消语义时先列出每项工作的所有者、底层资源是否支持终止、终止后会触发何种异常,以及结果还能否从缓存被其他消费者复用。只有这些问题有明确答案,停止函数才不只是一个布尔开关。

取消还不等于业务回滚。浏览器中止读取响应,并不能保证服务器没有收到写请求;服务器可能已经扣减库存或创建记录。因此只读搜索可以把中止作为节省资源的手段,写操作则必须配合幂等键、服务端状态查询和明确的重试协议。前端不能因为收到中止异常就显示“操作未发生”,也不能把取消按钮包装成无法兑现的撤销承诺。

第三种语义是消费者离开但共享工作仍有价值。例如两个组件请求同一资源,一个卸载时不应中止另一个仍在等待的共享请求。此时缓存层拥有底层控制器,组件只取消自己的订阅;当引用计数归零或缓存策略决定淘汰时,缓存层才终止网络工作。把控制器藏在任意一个组件内会让所有权随机依赖挂载顺序。

每一轮执行持有独立控制器与序号

可取消查询的最小状态包含当前执行序号、当前控制器、加载值、成功数据和可展示错误。参数变化时先终止上一控制器,再创建新的控制器并递增序号,将信号显式传给获取函数。响应到达后同时检查信号是否已中止以及序号是否仍为当前轮,只有两者都成立才提交结果。双重检查可以覆盖底层函数不完全尊重信号或在中止前已经完成解析的情况。

控制器不能在模块顶层或多个组件实例之间无意共享。一个实例终止自己的搜索,不应让另一实例的请求一起失败。它也不能在中止后复用,因为中止信号是不可逆状态;下一轮必须新建控制器。若组合式函数允许调用方传入外部信号,可将外部中止转发到本轮控制器,并在本轮结束时移除转发监听,防止长生命周期信号积累闭包。

序号保护要放在所有异步边界之后,包括响应读取、数据解码、人工延迟、工作线程回复和派生计算,而不只放在第一次网络等待之后。旧响应可能先完成下载却在大型解析上落后于新响应,如果解析结束后无检查仍会覆盖当前值。测试可以让不同阶段受控暂停,主动构造旧轮在任意一步晚于新轮完成的顺序。

用作用域清理统一收口生命周期

组合式函数可能在组件安装流程、嵌套作用域或测试创建的效果作用域中运行,不应只依赖组件卸载钩子。注册作用域清理,让所在响应式作用域停止时统一中止当前执行、清除计时器、退订事件并关闭仅属于该实例的通道。这样调用方通过停止作用域进行资源管理时,组合式函数不会留下后台任务。

侦听源的清理回调适合处理“上一轮”的资源:每次回调开始一项工作,并在失效时中止该轮。作用域清理则是最终保险,处理组件永久离开。两层清理都要幂等,重复中止和重复移除监听不能抛错。若清理需要异步等待,不要假设框架会阻塞卸载;应先同步阻止新提交,再尽力释放远端资源并记录失败。

组合式函数返回的显式停止方法,应调用与作用域清理相同的内部收口路径,而不是维护第二套状态。停止后是永久关闭还是允许再次执行必须写进接口契约。永久关闭的实例收到刷新调用应返回明确结果;可重启实例则要创建全新控制器和订阅。含糊处理常导致界面显示停止,后台侦听却因下一次参数变化自动复活。

加载、错误和取消必须是不同状态

取消通常是预期控制流,不应进入与网络断开、权限拒绝相同的错误提示。捕获异常时先依据本轮信号与标准化的中止类型判断是否取消,取消轮只结束自己的加载占用,不覆盖当前成功数据,也不弹出危险提示。不能单凭异常文本包含某个英文词判断,因为不同运行时和请求库会产生不同消息,业务异常也可能碰巧使用相同文字。

加载状态不宜由一个布尔值在每轮结束时直接改为假。旧轮的结束回调可能发生在新轮仍运行时,若不检查序号就会提前关闭加载指示。可以令状态记录当前执行序号,或维护属于该实例的进行中集合,只允许当前轮结算可见状态。对于分页并行请求,还应分别表示首屏加载、追加加载和静默刷新,避免一个请求取消使其他请求的反馈消失。

保留旧数据还是在新请求开始时清空,是产品语义而非取消实现细节。搜索建议通常可以保留结果并标注正在更新,权限作用域改变时却必须立即清除旧数据,防止在新权限验证期间展示不再允许的信息。组合式函数可接收明确策略或由上层在关键参数变化时清空,但不能对所有场景默认“保留更顺滑”。

去抖、超时与取消需要一条时间线

输入去抖期间还没有发出网络请求,但已有一个待触发计时器。新输入到来时要清除旧计时器;组件离开时也必须清除。若上一轮请求已经发出,选择是立即中止它,还是等新输入通过去抖后再中止,取决于是否愿意让旧结果短暂完成。前者节省资源且语义简单,后者可能提高缓存命中,却必须确保旧结果不会被误认为新查询结果。

客户端超时可以通过计时器中止本轮控制器实现,但要区分用户取消、参数失效与超时,三者的重试提示不同。超时计时器在成功、失败或其他原因中止后都要清除,避免它稍后中止已被错误复用的控制器。超时只说明客户端等待预算耗尽,不证明服务器停止处理;对写操作仍需以幂等键查询最终状态。

重试应为每次尝试创建子控制器,同时受整轮外部信号约束。退避等待本身也必须可取消,否则页面离开后计时器仍会唤醒并发起下一次请求。只对确定可重试的传输失败和服务端暂时错误重试,权限失败、输入校验和主动取消立即结束。将尝试次数、最终原因与总耗时写入脱敏遥测,才能分辨用户频繁改参和真正的网络不稳。

流、事件与工作线程也要遵守所有权

可读流中止请求后,还要释放读取器锁并停止消费循环;循环每次读取后检查当前轮身份,避免已排队的数据块继续写入状态。服务器推送或套接字若由组合式函数独占,停止时应移除事件监听并关闭连接;若连接被应用共享,则只退订当前主题,不得关闭全局通道。资源所有权应体现在创建位置和返回接口上,而不是靠调用者猜测。

工作线程的终止比忽略消息更彻底,但共享线程不能被一个组件任意终止。独占线程在作用域清理时调用终止,并将待处理任务全部标为取消;共享线程给任务附加实例标识和序号,取消时发送协议消息并在主线程丢弃迟到回复。工作线程内部的长循环也需定期检查取消标志,否则取消消息只能排在耗时任务之后处理。

事件监听器的移除要求保持相同函数引用和选项,临时匿名函数会让清理失效。将处理器限定在本轮或实例范围,注册后立即把对应释放函数纳入清理集合。验证时反复挂载、改变参数和卸载数百次,观察监听器、活动请求、计时器及线程数量是否回到基线,而不仅检查页面最后显示的文本。

用可控调度器穷举竞态顺序

单元测试不要依赖真实网络速度,而应使用可手动完成、拒绝和响应中止的伪获取函数。构造甲轮开始、乙轮替换、乙先成功、甲迟到成功的顺序,断言最终只提交乙;再让甲在响应后解析阶段暂停,证明序号检查覆盖第二个异步边界。检查取消异常不进入错误状态,当前轮真实失败则保留可重试信息。

生命周期矩阵包含参数快速连续变化、去抖尚未触发就卸载、请求进行中卸载、超时与成功同一时刻竞争、重试退避中停止、外部信号先中止以及显式停止后再次调用。对每种顺序统计控制器创建与中止次数、计时器余量和状态提交日志,确保清理恰好覆盖所有已创建资源且不会终止下一轮。

集成验证在慢速网络下快速输入并往返路由,服务端记录实际断开情况,浏览器检查无未处理拒绝和卸载后状态写入。写操作场景额外模拟响应丢失但服务端已成功,确认界面通过幂等状态查询恢复,而非因客户端中止重复创建。取消设计只有在结果顺序、资源释放和业务最终状态三方面都可证明时才算完成。

实现片段

if (requestId === activeRequestId.value) data.value = result

一手参考资料

评论 · 0

还没有评论留下第一句经过思考的话。

游客评论需审核。注册后可直接公开,无需审核。

SHARE / 分享

分享这篇文章

WECHAT / 微信

用微信扫一扫

在手机微信中打开文章后,再从微信右上角分享给朋友或朋友圈。