把它做成生产功能之前,需要先拆开协议事实、产品承诺和运行成本,三者混在一起很容易得到错误结论。 IndexedDB 事务在事件循环没有待处理请求时可能自动提交。把网络 await 放在读写中间,常会得到 TransactionInactiveError;把块和 manifest 分开事务又会留下只更新一边的崩溃窗口。
浏览器能力由版本、权限、页面可见性和设备策略共同决定。实现需要能力探测与可解释降级,不能把某一台开发机上的 API 存在当成产品契约。
落地方案的关键部件
落地时要把关键选择变成稳定契约:输入如何校验、状态由谁推进、资源何时释放、旧版本如何降级。只有这样,后续优化才不会改变原有语义。
- 网络读取在事务外完成,事务内只做同步发起的块写入、区间合并和状态更新。
- 以任务和块索引作为幂等键,重复到达不会增加已接收字节。
- 升级 schema 用明确版本迁移,失败时保留旧数据并给出恢复或清理路径。
持久化可信度来自同一事务里的数据与索引,而不是 UI 记住的百分比。先定义崩溃不变量,再安排 IndexedDB 请求。
别让错误假设进入生产
生产问题往往发生在两个正确动作同时到达时。需要逐条检查迟到消息、重复执行、资源耗尽和版本交叉,而不是只补当前堆栈显示的那一行。
- 先更新 manifest 再写块,存储配额失败后刷新会跳过不存在的数据。
- 长事务等待网络阻塞其他任务,浏览器随后中止并回滚全部写入。
- 进度来自内存计数而非已提交记录,崩溃后数字倒退且无法解释。
发布闸门应该检查什么
验证时同时观察两端状态、持久化记录和可观测指标。单看某个按钮或一次接口响应,无法证明整个闭环成立。
- 在块写、manifest 合并和 commit 前后逐点终止页面,重启后核对不变量。
- 重复、乱序和并发写同一块,断言存储唯一且进度只计算一次。
- 耗尽配额并触发 schema 升级失败,确认错误可操作且旧任务不被破坏。
完成并不等于主路径跑通,而是每个终态都能对账、每个自动动作都受用户意图约束、每个成本都有明确上限。