题目重点:用可控延迟重现旧请求晚返回,再用 generation 或取消机制保护当前 UI,同时分清网络取消与服务端副作用。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版连续查询 A、B 的响应可能反序到达;只有最新输入的响应可更新当前界面。
- 如果是支付/创建等有副作用请求,前端取消 fetch 不代表远端操作回滚;须依赖服务端幂等或状态查询。
- SDK 封装可能是 Promise/callback/event emitter,具体 API 未公开;修复位置在拥有请求生命周期的最小边界。
解题路线
- 先让客户描述可重复步骤、预期/实际、浏览器/SDK 版本和网络条件;用延迟注入建立 A 慢/B 快的最小用例,并从时间线区分旧响应覆盖、重复请求、卸载后 setState 和共享 mutable 变量。
- 对只读查询,在每次发请求时递增 generation,响应到达时若 token 不是当前 generation 就丢弃;或用 AbortController 取消上次请求,同时在回调中仍检查 generation 防竞态。
- 把 loading/error/data 绑定到同一代请求,不让 A 的 finally 在 B 尚未完成时清掉 B 的 loading,也不让 A 的 error 覆盖 B 的 success。组件卸载时清理订阅/取消本地更新。
- 若竞争在 SDK 内部,请检查共享请求参数对象是否被下一次调用改写、缓存 key 是否缺字段、回调是否绑定了最新状态。修复后做高延迟、快速输入、卸载、重试和错误交错回归。
- 面试 roleplay 先复述客户问题、说明复现与证据、提出最小安全修复和短期规避,再给验证和升级路径;不要一上来要求客户安装陌生补丁。
手动走一遍
训练时序 t0 查询 A,t1 查询 B;B 在 t2 成功,A 在 t3 才成功。没有保护时 A 覆盖 B;generation 分别为 1/2,t3 的 1≠当前 2,故丢弃 A。若 A 是创建付款,丢弃 UI 响应并不撤销已创建的付款,需查服务端状态。
规模与取舍
每次发起/完成只做 O(1) generation 比较与状态更新,额外 O(1) 状态;请求数量和服务端负担需防抖、取消或限流,但取消不等于事务回滚。
易错点与追问
容易失分的边界
- 只在成功回调检 token,旧请求的 error/finally 仍修改当前状态。
- 认为 AbortController 一定阻止已经到达的响应回调。
- 前端防旧写解决了 UI,却把有副作用的重复服务端调用留着。
- 没有源码仍臆断面试具体 bug 行号。
- 客户调试时只给技术术语,不提供可复现证据与修复验证。
继续深挖
- 多个并行分页请求都应保留时,单一 generation 为什么不够?
- SDK 内共享 token 刷新队列发生竞态,怎样避免所有请求同时刷新?
- 客户已见到重复支付,如何先查账再决定是否重试?
