在让 Agent 修改界面前,说清加载、取消与迟到结果

使用 AI 辅助编写与复核。

本文目录

用一组有顺序的事件描述异步界面行为:用户选了什么、哪个请求属于这次选择,以及结果到达时屏幕应显示什么。还要包括一种情况:旧请求尚未结束,用户就再次回到了同一个项目。

这样,编程 Agent 才有一份可检查的行为契约。“修一下加载动画”没有说明哪个结果可以替换页面、哪个错误可以显示,也没有说明加载指示应在何时停止。

选定一个具体交互

设想一个虚构的项目预览面板。选择项目后,应用开始只读的预览工作。在这个例子中,每次选择都会发起新请求,不会隐式复用缓存。新的选择会清空上一次预览,并显示包含所选项目名称的加载提示。

下面六个事件是原创的示意需求,不是已复现的缺陷或执行日志。我们特意设定:应用请求取消后,旧的应用层完成事件仍然到达。这里没有提供请求实现,也没有声称被中止的 fetch 一定会在之后成功完成。

写出六个事件的时间线

事件 发生了什么 当前视图归属 预期可见状态
E1 用户选择 A;请求 p1 启动 A / p1 正在加载 A;尚无预览
E2 用户选择 B;请求 p2 启动 B / p2 清空 A 的预览区域;显示正在加载 B
E3 应用请求取消旧 p1;尚未确认工作已停止 B / p2 保持正在加载 B;不显示 B 的错误,也不清除其加载状态
E4 p2 先于 p1 完成 B / p2 显示 B 的预览;B 不再加载
E5 用户再次选择 A;新请求 p3 启动 A / p3 清空 B 的预览区域;显示 p3 正在加载 A
E6 旧 p1 的完成事件到达 A / p3 对当前可见视图忽略 p1;保持 p3 正在加载 A

完成顺序是 p2 在 p1 之前。在 E6,只检查当前选中的项目是不够的:旧 p1 和新 p3 都对应 A。

结果必须属于当前请求,不能只属于一个同名项目。在这个案例中,只有完成事件与当前项目、当前请求都匹配,而且该请求仍允许完成视图更新时,结果才能填入面板。

区分“取消”的三种含义

MDN 的 AbortController 文档介绍了通过 AbortSignal 与异步工作通信,以及中止受支持的浏览器操作。它没有替这个面板决定产品行为。这里的可见状态规则是本案例选定的设计。

动作 它可以表达的要求 不能据此假定什么
请求取消 通过相关操作支持的机制,请求其停止 所有产生结果的工作都已停止,或服务器端改动已被撤销
丢弃结果 阻止过时的完成事件修改当前视图 产生结果的一方没有做过工作,或远端操作从未完成
撤销已完成的服务器动作 执行另一项受支持的操作,恢复某种状态 浏览器中止或忽略响应会自动完成这种撤销

这个面板是只读的。如果真实请求会保存或修改远端数据,应另外明确该操作的状态和撤销契约。隐藏结果是显示层的决定,不是其效果已被撤销的证据。

为每种状态定义归属

到来的动作或事件 归属条件 预期视图行为
开始一次选择 新的当前项目/请求组合 清空之前的预览,显示带项目名称的加载状态
当前请求成功 与当前组合相同,且仍等待完成 显示它的预览,结束它的加载状态
当前请求失败 与当前组合相同,且仍等待完成 显示该请求的错误,结束它的加载状态
请求取消一个旧请求 该请求已不再拥有当前视图 保持当前请求的状态不变
用户停止当前请求 明确将该请求标记为不再允许完成视图更新 为所选项目显示“预览已停止”;不装入结果
当前请求被停止后,完成事件到达 已停止的请求不再允许更新视图 保持停止状态,不重新填入面板
旧成功或旧错误 不属于当前等待完成的视图 不替换内容、不显示其错误,也不停止当前加载动画
旧清理通知 属于已过时的请求 不清除另一个请求的加载状态

“预览已停止”描述的是选定的界面状态。如果尚未确认产生结果的工作已停止,就不能把这个标签变成所有后台工作都已停止的承诺。

这些规则也覆盖迟到的失败。不能仅仅因为用户又在查看 A,就把 p1 的错误显示成 p3 的失败。

明说策略选择,不把它藏起来

不同产品可以合理地采用不同做法。本例明确选择如下:

决策 另一种做法 选定的策略与理由
新请求期间如何处理旧内容 保留旧内容,并明确标为上一次结果 清空旧内容,让这个紧凑面板只对应一个可见归属
A 正在停止时是否启动 B 等待停止确认 不等待就启动 B,并让旧事件不影响 B 的视图
回到 A 时怎么处理 复用一个已明确标识的缓存结果 启动 p3;本练习没有定义缓存策略
停止当前预览后怎么处理 继续接受它最终到达的完成事件 将其标记为不再允许更新视图,让停止动作产生稳定的可见结果

另一种策略本身并不是缺陷。在修改状态处理前,编程 Agent 需要知道产品究竟想采用哪一种。

把症状变成可观察的请求

一段虚构的口述可能是:

“我先点 A,再点 B,结果回来的顺序很奇怪。有时我还会回到 A。让屏幕一直保留正确的预览。”

不预设实现方式的请求可以写得更精确:

任务:让项目预览面板跟随当前请求。

把六个事件 E1–E6 作为预期行为案例。
只用所选项目作为身份是不够的:p1 和 p3 都对应 A,
但最后等待完成的视图只属于 p3。

选定行为:
- 每次选择都发起新请求;不隐式复用缓存。
- 清空上一次预览,显示“正在加载 [所选项目]”。
- 只为当前、仍允许更新视图且仍等待完成的请求装入成功或错误。
- 旧完成、旧错误或旧清理事件不能替换当前预览,
  也不能改变其加载或错误状态。
- 主动停止当前请求时,显示“预览已停止”,
  并使该请求之后不再允许填入视图。
- 使用实际支持的机制请求停止过时工作。
  不要把停止请求描述成已确认的服务器回滚。

保留:
选择控件与预览内容格式。
不要增加撤销功能、修改服务器端数据或重新设计导航。

返回:
当前事件与归属处理路径、有针对性的改动提案,以及事件案例的检查记录。
记录实际采用的实现假设;未执行的检查与已观察行为分开保留。

检查事件顺序,而不只看一张截图

拟进行的检查 预期结果 实际观察
先交付 p2,再在 E5 之后交付旧 p1 E4 显示 B;到 E6,p1 不替换 p3 等待完成的视图,也不结束其等待状态 —
将旧 p1 的成功改为旧错误后交付 不把它装入为当前 p3 的错误 —
在 p3 等待完成时交付旧 p1 的清理通知 p3 仍显示加载中 —
停止当前 p3,再交付 p3 的完成事件 保持停止状态;不装入预览 —
未停止 p3 时交付当前 p3 的成功事件 显示 A 的 p3 预览,结束它的加载状态 —

表内结果均为人工推演的预期。产生结果的工作、取消支持、事件路由、缓存行为与任何远端副作用,都需要检查实现后才能确定。本指南没有实际运行其中任何一项。

一份有用的请求会说清每一步的可见视图属于谁。这样,实现者才能解决真正的时序问题,同时分开处理取消、显示策略与已完成动作的撤销。