如何利用 Web Locks API 跨标签页协调复杂的本地数据库写操作需要先看清适用场景和关键步骤,避免只记结论却忽略实际限制。
Web Locks API不能直接防止跨标签页数据库冲突,但可通过统一锁名、await锁请求、将IndexedDB写操作封装进锁回调并await tx.done来构建可靠协调机制。
不能直接防止,但能构建可靠的协调机制。Web Locks API 本身不锁数据、不锁文件、不干预 IndexedDB 或 localStorage 的执行逻辑,它只提供一个浏览器级的命名锁(lock)注册与等待协议。是否发生冲突,取决于你是否在所有写操作前主动申请同一把锁,并严格遵守“拿锁 → 操作 → 释放”的顺序。
常见错误现象:DOMException: The operation is insecure(未在 secure context)、AbortError(锁被中断)、或看似加了锁却仍有并发写入(没统一锁名或没 await navigator.locks.request())。
await navigator.locks.request('db-write', cb),不能只调用不 await —— 否则锁立即释放,形同虚设'db-write')需全局一致,且建议带业务上下文,比如 'db-write-user-profile',避免不同模块误共享关键不是“锁住 IndexedDB”,而是把写逻辑包进 navigator.locks.request() 的回调里,确保同一时刻最多一个标签页执行该段逻辑。注意:锁持有期间浏览器仍可响应其他事件,但你的写函数不会并行进入。
async function safeUpdateUser(id, data) { return navigator.locks.request('db-write-user', async (lock) => { const db = await openDB(); // 自定义封装的 indexedDB 打开逻辑 const tx = db.transaction('users', 'readwrite'); const store = tx.objectStore('users'); await store.put(data, id); return await tx.done; // 等待事务真正完成再释放锁 });}
await tx.done,而非仅 tx.commit() —— 后者不保证写入落地,锁提前释放会导致其他标签页读到脏数据lock.release()
用一把全局锁(如 'db-write')最简单,但会串行化所有写操作,哪怕它们修改的是完全无关的对象。分粒度锁(如 `db-write-${userId}`)能提升并发度,代价是逻辑变重、锁名管理易出错,且无法解决跨粒度的复合操作(如“转账”需同时改 A 和 B 账户)。
`db-write-note-${id}`,锁粒度细,体验好'db-write-order-transaction',否则 A 标签页锁 note,B 标签页锁 user,两者同时跑就崩Web Locks 是 best-effort 机制:页面关闭、崩溃、长时间后台冻结(如 iOS Safari 进入休眠)都会导致锁自动释放,且不通知其他持有者。这意味着你永远不能假设“锁还在,所以数据一定安全”。真实系统必须叠加最终一致性校验。
lastModified 时间戳,读取时比对,发现旧值就拒绝更新或触发合并逻辑lock.mode === 'exclusive' 做业务判断——这个值始终是 'exclusive',API 不支持共享锁最常被忽略的一点:锁只在当前 browsing context 生效,iframe 与父页默认不共享锁空间,除非它们同源且显式使用 new LockManager({ type: 'shared' })(目前非标准,仅 Chromium 实验性支持)。生产环境请一律按独立上下文处理。