假设你在搜索框里输入“手机”,等了一会儿没有结果,于是改成“耳机”。列表终于出来了,刚想往下看,内容却又变回了手机。
输入框没变,结果变了。
如果你的搜索页、筛选列表或地址选择器也出现类似现象,排查时可以先问一句:这份结果,属于用户当前的那次操作吗?
弱网测试里,页面能不能加载出来当然要看。加载出来的内容对不对,同样值得检查。
先发出的请求,不一定先完成
把刚才的操作拆开,会得到两个请求:A 查询手机,B 查询耳机。
A 发出以后,用户修改了输入,B 随后发出。两次请求的处理时间可能不同,缓存、服务端处理以及各自的连接情况也可能不同,返回顺序未必与发送顺序一致。
假设 B 先回来,页面展示耳机;过了一会儿,A 才回来。如果两份响应都能直接修改同一个列表,手机就会覆盖耳机。

图 1|请求时序示意,不代表某款应用的实测结果。问题出在旧响应仍然能更新当前列表。
这种情况通常被称为竞态。React 官方的数据请求示例也专门演示了忽略过期响应的处理方式,因为网络响应可能以不同于发送时的顺序到达。
这并不是某个框架专有的问题。只要多个异步任务都能更新同一块界面,就需要说明谁还有更新资格。
也别把它与 TCP 数据包乱序混为一谈。这里讨论的是两个业务请求的完成顺序,不是在说同一个响应的字节可以随便乱着交给页面。
防抖减少请求,却不负责判断结果归属
搜索页经常做防抖:用户停下来一小段时间后,再真正发起查询。这样可以少发一些没有必要的请求。
但只要旧请求已经发出,防抖就不能让它自动消失。用户停下来搜过一次,随后又换了关键词,依然可能有两次查询同时进行。
另一种处理是取消旧请求。这有助于减少多余工作,但界面仍需要检查响应是否已经过期,尤其要覆盖请求已经完成、结果等待分发等边界。
一个常见办法,是给每次查询分配递增的编号。新查询开始后,更新“当前查询编号”;回调准备写入页面时,先确认自己的编号仍然有效。
这个检查要覆盖结果列表、错误提示和加载状态。 只挡住旧数据,却让旧请求的失败回调弹出错误,或者让旧任务提前关闭新任务的转圈,页面仍然会出问题。
先把页面调慢,给操作留出时间
正常网络下,前一个请求可能在用户改词之前就完成了,很难看清两个操作交错时的表现。
这里用我自己开发的 Android 工具「弱网管家」演示参数设置。先在首页选择要测试的应用,再添加自定义模板,逐步增加延迟。工具负责制造网络条件,是否出现旧结果覆盖,要看被测应用的实际表现。

图 2|Pixel 6 真机截图。先选择被测应用;图中尚未选择目标,也未开始测试。
可以从下面三轮开始。数字只是探索性起点,适合拉长等待窗口,不代表真实地铁或某种移动网络的标准参数。
| 轮次 | 条件 | 主要观察 |
|---|---|---|
| 正常对照 | 原始网络条件 | 搜索流程本身是否正常 |
| 增加延迟 | 上、下行延迟各设为 500 ms | 旧查询未结束时再次搜索的表现 |
| 增加波动 | 在上一轮基础上,上、下行抖动各设为 150 ms | 多轮操作交错时,页面状态是否一致 |
先不叠加限速和丢包,减少需要解释的变量。记录原始网络、应用版本、缓存状态和实际操作顺序,后续回归时一起保留。

图 3|Pixel 6 上的配置演示:双向延迟 500 ms、双向抖动 150 ms。参数是模拟设置,不是接口耗时测量值。
上下行各填 500 ms,也不能直接得出“接口一定多花 1 秒”。一次业务请求涉及哪些数据包、连接是否复用、服务端处理多久,都会影响最终等待时间。
同样,加入抖动不保证 A、B 一定交换返回顺序。如果需要稳定验证“旧请求最后完成”,可以让受控测试接口明确延后 A 的响应,再用真机弱网测试补充实际交互观察。
别只连续输入两个关键词
搜索词切换只是最容易讲清楚的入口。沿着同一个问题,还可以检查下面几种操作。
清空搜索框。 旧请求回来后,页面是否又被填满?空输入究竟意味着展示推荐内容,还是清空结果,应当先有约定。
切换筛选条件。 关键词相同,价格区间、排序或分类不同,仍然是不同查询。只检查关键词相等,可能拦不住旧筛选条件对应的响应。
离开页面再回来。 上一次页面实例的回调,是否会影响新页面?新旧页面显示相同关键词,也不能据此认定两次请求可以互换。
让旧请求失败。 当前查询已经成功,之前的请求才超时,此时是否突然弹出“搜索失败”?这个问题只看成功结果很容易漏掉。
每轮固定操作顺序,记录两次输入的间隔。若操作会触发防抖,也要记下请求实际发出的时刻,不能把键盘输入时间直接当成网络请求开始时间。
修好以后,要检查整个页面是否属于同一次查询
一条“搜索成功”的验收太宽了。更有用的检查是把页面几块状态放在一起看。
| 检查位置 | 应当确认的行为 |
|---|---|
| 输入框与列表 | 当前条件与展示结果一致 |
| 加载动画 | 由当前有效查询控制,不被旧任务提前结束 |
| 错误提示 | 旧查询失败不误报当前查询失败 |
| 分页结果 | 不混入旧关键词、旧排序对应的下一页 |
| 返回与重进 | 旧页面的结果不写入新页面 |
如果同一个关键词可以连续刷新,只用关键词作标识也不够。两次内容相同的输入,仍然可能对应两次不同的查询意图。
保存网络参数很有帮助,但回归用例还应保留触发步骤和预期结果。模板告诉别人“网络怎么设”,操作记录才能告诉别人“怎样把问题问出来”。
下一次,看看最后留下的是谁的结果
弱网下最显眼的是慢,最容易被忽略的却可能是一次悄悄发生的覆盖:列表有内容,页面没有报错,只是它已经不再回答用户眼前的问题。
给页面增加等待时间以后,用户会继续输入、改条件、返回、重新进入。每一个新动作,都可能让之前的响应失去更新页面的资格。
下次检查搜索页时,可以连续换三次关键词,再清空一次输入。别急着看加载用了几秒,先确认最后留下的内容,是否仍然属于最后一次操作。
1054



被折叠的 条评论
为什么被折叠?



