
先看一个常见的时序问题
用户打开任务列表,界面发出第一次查询。随后用户切换筛选条件,界面发出第二次查询。第二次查询先返回,页面显示了正确的新列表;第一次查询稍后返回,如果代码无条件赋值,就会把旧列表重新显示出来。
这个问题并不要求服务器出错,也不需要极端网络环境。只要两个请求的耗时不同,就可能出现。把查询间隔调长只能降低出现概率,不能改变响应乱序的事实。
请求序号保护当前视图
一种简单方式是在发起请求前增加序号,响应返回后比较当前序号。只有仍然对应最新请求的结果才允许修改界面。序号应按需要隔离:不同组件或独立查询不应共享同一个全局序号,否则一个页面刷新可能误取消另一个页面的结果。
伪代码可以表达为:先令 currentSequence 加一,保存到 localSequence;等待查询完成;若 localSequence 不等于 currentSequence 就丢弃结果。错误提示与 loading 状态也需要同样的判断,否则旧请求的失败提示仍可能覆盖新请求的成功界面。
取消请求和拒绝旧结果不是一回事
在支持取消的客户端中,可以在新查询发起时取消旧查询,以减少无用的传输和解析。但取消信号可能发出得太晚,服务端也可能已经完成处理。因此,界面仍应保留响应序号校验。
对于产生业务副作用的请求,更不能把取消请求理解为取消业务。关闭页面或中止连接并不代表服务器回滚。创建、支付、发布等操作应使用各自的业务协议确认结果,而不能沿用普通列表查询的处理方式。
任务重试需要更稳定的身份
如果任务允许重试,只有任务 ID 通常还不够。第一次执行的迟到响应可能在第二次执行开始后返回。可以将任务 ID 与 attempt_id 或执行版本一起传递,客户端和服务器都核对该响应是否属于当前尝试。
这里的版本不是显示用的“第几次”文字,而是状态更新的约束。数据库更新可以要求当前版本与请求携带版本一致,更新行数为零时说明请求已过期或状态被其他执行者推进,调用方应读取最新记录,而不是强行覆盖。
状态本身也有不可逆的边界
同一次执行中,已经确认完成的结果不应被较旧的处理中快照覆盖。可以在合并数据时同时考虑执行身份、状态版本和终态证据。单看 updated_at 容易受到时钟差异、接口缓存与字段格式影响;最好由服务端提供稳定的递增版本或受约束的状态转换。
这不意味着所有终态永远不能改变。比如业务允许撤销、审核后驳回,就应把这些动作定义为明确事件,并记录新的版本。不能把一条过期轮询响应和一次真实撤销混在同一条覆盖规则中。
让测试覆盖响应顺序
可以在测试中准备两个可手动完成的 Promise,先发出旧查询,再发出新查询,先完成新查询,最后完成旧查询。断言页面仍保留新结果,并检查旧查询的异常不会改变提示或 loading。还应测试组件卸载后响应返回、筛选条件连续变化,以及任务重试后上一轮请求才完成。
这些用例不需要真实弱网,也不依赖随机延迟,因而更容易稳定复现。真实网络测试可作为补充,用来发现连接、缓存或会话相关问题。
把用户看到的状态变成可信结果
可靠的状态展示来自三个层次:客户端拒绝过期视图响应,业务执行使用明确身份,服务端约束状态更新顺序。分别建立这些边界,比在页面上增加越来越多的刷新按钮更容易维护。
本文由 AI 辅助整理,场景和字段为设计示例,配图为流程示意,不代表具体系统的实测性能。