Web Locks API 通过统一锁名、完整包裹 IndexedDB 事务并 await tx.done 实现跨标签页写操作串行化;常见错误包括未 await 锁请求、锁范围过小、漏掉 await tx.done 及锁名粒度粗导致无关操作阻塞。

Web Locks API 本身不拦截 IndexedDB 写操作,但能强制所有标签页串行执行写逻辑——前提是锁名统一、回调内完整包裹事务、且 await tx.done。
为什么直接调用 navigator.locks.request() 还会写冲突
常见错误是只“申请锁”却不 await 它的返回 Promise,或把锁范围缩得太小(比如只包 put() 调用)。
-
navigator.locks.request('db-write')必须用await,否则锁立即释放,后续代码并行执行 - 锁回调里不能只做
store.put(),必须包含openDB()→transaction()→put()→await tx.done全流程 - 漏掉
await tx.done是高频坑:IndexedDB 的tx.commit()不保证写入落地,锁提前释放后其他标签页可能读到旧值 - 锁名若写成固定字符串如
'db-write',会导致用户 A 编辑笔记时,用户 B 的购物车更新也被阻塞——应按资源粒度命名,例如`db-write-note-${noteId}`
如何在写操作中正确集成 tx.done 和锁生命周期
关键不是“锁住数据库”,而是让整个事务的原子性与锁的持有期严格对齐。
- 回调函数必须返回 Promise,且该 Promise 应 resolve 自
tx.done(而非store.put()) - 不要在锁内做网络请求、加密、DOM 渲染等耗时操作;这些应移出锁外,仅保留“读-改-写”最小闭环
- 若写操作失败(如 key 重复、quota 不足),锁仍自动释放,无需手动调用
lock.release() - 示例片段:
await navigator.locks.request(`db-write-note-${noteId}`, async (lock) => {
const db = await openDB();
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
// 可选:先 get() 校验版本,实现乐观锁辅助
await store.put(updatedNote, noteId);
return await tx.done; // ← 这一行决定锁何时真正释放
});
锁名设计与 Safari 兼容性必须同步考虑
锁名不是标识符,而是作用域开关;而 Safari 完全不支持 Web Locks API——这两点必须一起处理。
- 锁名需 URL-safe,避免空格、斜杠;长度建议 ≤64 字符,过长可能影响内部哈希性能
- 别用
'cart'或'draft'这类泛化名,应带业务唯一 ID:`user-cart-${userId}`、`note-lock-${noteId}` - Safari(iOS 17.5 / macOS 14.5 及更早)无任何支持痕迹,
if ('locks' in navigator)不能作为兜底逻辑 - 服务端幂等是底线:每个写请求附带客户端生成的
requestId,后端校验是否已处理 - 前端轻量 fallback 可用
localStorage+storage事件模拟(例如存lock:note-123:ts),但仅作提示,非强一致
真正容易被忽略的是锁的作用域边界:私有窗口、跨源 iframe、不同设备之间完全不共享锁;而锁内执行时间越长,其他标签页排队等待就越明显——它解决的是“谁先写”,不是“怎么写更快”。

















