保存请求回来时,文档已经变了
表单设计器允许用户编辑字段配置、保存草稿,也可以随时切换到另一份表单。保存请求发出去后,页面并不会停止:用户还可以输入、切文档,甚至更换账号或权限。
如果保存成功就统一清掉“未保存”标记,很容易丢掉请求期间的新修改。这里讨论的是客户端如何处理迟到结果,不涉及让服务端撤回已经发生的写入。
保存成功,为什么仍然有未保存内容?
点击保存时发出的是一个快照。假设请求带走版本 10,用户继续编辑到版本 11,然后版本 10 保存成功。服务端确实保存了内容,但页面上的版本 11 还没有保存。
一种办法是在保存期间锁住编辑,处理简单,但用户需要等待。另一种是允许继续编辑,分别记录“已保存到哪个版本”和“现在编辑到哪个版本”。项目选择后者,成功后可以更新保存基线,却不能把新内容的 dirty 标记清掉。
切换文档还要更严格。保存 A 的时候打开 B,A 的结果不能改 B 的标题、内容或保存状态。
用取消请求或一个版本号,够不够?
取消等待能减少无用工作,但请求可能已经到达服务端。客户端不再接纳结果,与服务端有没有完成写入,是两个问题。
项目在操作开始时保存用户身份、权限版本、组件生命周期、当前文档和工作区代次,再记录内容版本。返回时先比较身份:已经换用户、换文档或重载工作区,就拒绝回写;身份相同而内容版本增加,则只处理原快照的保存事实,保留当前修改。
这些信息不能全部塞进一个递增数字。用户身份需要稳定,权限变化要让旧操作失效,内容变化则只说明又有新修改,处理结果并不相同。
项目用可手动完成的 Promise 和存储、网络替身制造这些交错:先暂停请求,继续编辑或切换身份,再放结果回来。它能检查客户端接受了哪份结果,真实多标签页行为和服务端写入原子性仍要在对应环境验证。
确认框打开后才降权,怎么办?
删除确认框打开时有权限,点击确认之前被降权,旧回调仍可能持有先前的角色。只把按钮隐藏或用 effect 关闭弹窗,未必来得及阻止它执行。
项目会给交互记录身份、权限和代次,执行确认动作前重新检查。如果中间还要读文件等异步操作,返回后再检查一次。关闭弹窗处理的是界面,拒绝旧回调处理的是已经启动的操作。
服务端仍需独立鉴权。前端降权、关页面或取消等待,都不能撤销已经被服务端接受的请求。
新建超时后,为什么不能直接再发一次?
更新已有文档时,客户端知道服务端 ID。新建则可能是请求已发出,还没拿到 ID 就超时:服务端可能没收到,也可能已经创建成功。直接重发有可能得到两份文档。
常见的处理思路是为同一次创建保留稳定身份,并由服务端识别重复请求。这个项目目前先在客户端登记:按用户和客户端文档 ID 保存进行中、已取得结果、结果未知三种状态。拿不准远端结果时保留未知,避免无条件再创建。
登记使用稳定的用户身份,不包含权限版本,否则降权再升权后,会查不到原来的请求。登记还限制每个用户的条目数和快照字节数,满了在请求前拒绝新任务,不能为了腾位置删掉未知条目,再打开重复创建的入口。
这份登记保存在内存里,尚不能解决进程退出或另一台设备提交的问题,服务端幂等仍需要服务端配合。
远端保存成功,本地草稿失败呢?
远端成功后,本地存储也可能写不进去。项目不会直接清 dirty,让用户误以为所有保存步骤都结束了;写回时还要比较内容版本,防止旧快照覆盖请求期间的新内容。
本地新格式草稿和旧格式兼容镜像也分别处理。新格式已写成功、只有镜像失败时,恢复优先读取新格式,避免再次从旧镜像恢复出过期内容。多标签页之间的并发写入,则还需要跨标签页的版本检查和锁策略。