处理如何设计支持离线优先(Offline-First)的大规模 IndexedDB 同步协议这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。
离线优先要求应用无网络时仍能完整响应操作、不丢数据、不阻塞UI,且同步须可中断恢复、具备变更追踪、顺序保证、幂等提交与局部失败隔离能力。
离线优先不是“先存 IndexedDB 再同步”,而是让应用在无网络时仍能完整响应用户操作、不丢数据、不阻塞 UI,且同步过程可中断、可恢复、可冲突解决——这要求同步协议必须自带变更追踪、顺序保证、幂等提交和局部失败隔离能力。
靠手动维护“已同步标记”字段极易出错;应利用 IndexedDB 本身的写时序和游标遍历能力,结合自增主键(id)或时间戳(updated_at)做增量快照。
put() 或 add() 并监听 onsuccess,确保写入完成后再记录变更元数据(如 pending_sync 对象)openCursor() 遍历 pending_sync store,按 id 升序读取——避免因游标跳过导致漏同步Date.now() 做排序依据:多个并发写入可能产生相同毫秒级时间戳;改用自增 version 字段或 IDBKeyRange.lowerBound() 配合上次同步最大 id
AbortError(如用户关闭标签页),需主动调用 transaction.abort() 并保留当前游标位置(存为 last_sync_id),下次从该点继续批量提交看似高效,但一旦中间某条失败(如 409 冲突、422 校验失败),整个批次回滚,重试成本高,且无法定位具体哪条出问题。
client_op_id(UUID v4),服务端据此幂等处理:重复收到同一 client_op_id 直接返回成功sync_status: "pending" | "synced" | "failed" 和 sync_error: string,而非全局开关{"server_version": 123, "conflict_fields": ["title"]}),客户端据此触发冲突解决流程,而非直接覆盖IndexedDB 没有 merge 语义;所谓“自动同步”只是掩盖了冲突——比如用户 A 改标题,用户 B 同时删整条记录,数据库层面无法判断“删优先”还是“改优先”。
server_version(即上次成功同步时服务端返回的版本号),服务端比对发现不一致即返回 409GET /api/items/:id?version=123),再调用业务定义的 resolveConflict(local, remote) 函数——这个函数不能是通用算法,必须由产品明确规则(如“编辑优先于删除”或“最后编辑者胜”)server_version,原失败记录标记为 resolved,不删除,留作审计put() 覆盖时忽略 version 字段:这会导致本地版本号停滞,下次同步必然再次冲突万级 pending 记录一次性加载进 JS 内存会卡死页面;游标遍历本身不卡,但每条都发 fetch 就会堆积大量 Promise,触发浏览器连接数限制和内存泄漏。
requestIdleCallback() 分片处理:每次最多处理 20 条,处理完主动 yield,等空闲再继续signal(AbortController),在用户切走标签页或手动暂停同步时立即中止所有未完成请求.then() 回调);只传必要字段(id, op_type, payload)clear() 不要用于“清空失败队列”——它不可逆且无事务回滚;改用批量 delete() 并检查返回的 result,失败则重试单条真正难的不是实现同步,而是定义清楚哪些操作允许离线发生、哪些必须在线校验(如支付)、以及冲突规则是否被所有终端一致执行。协议越“聪明”,越容易在边界 case 里暴露逻辑裂缝——先跑通单设备离线写 + 单次同步,再加多设备、再加冲突,别一上来就设计“终极同步引擎”。