web locks api 不能替代 indexeddb 事务,但可解决多标签页写入竞态;需将「读-改-写」逻辑包进 navigator.locks.request() 回调,并 await tx.done 确保锁不提前释放。

Web Locks API 不能代替 IndexedDB 事务,但能解决多标签页间的写入竞态——关键在于把「读-改-写」整段逻辑包进 navigator.locks.request() 回调,并 await tx.done 确保锁不提前释放。
为什么 IndexedDB 自带的 readwrite 事务不够用
每个标签页调用 indexedDB.open() 都会创建独立连接,事务只在当前连接内保证原子性。两个标签页同时对同一条记录执行 put(),浏览器不会自动协调顺序:可能都读到旧值 → 各自计算新值 → 先后写入 → 后者覆盖前者,形成静默数据丢失。
这不是数据库报错,而是业务逻辑层的竞态。你看到的错误可能是 AbortError 或长时间无响应,本质是隐式排队超时,不是传统死锁。
- 事务隔离范围仅限单个 connection,跨标签页无效
-
onupgradeneeded阶段尤其危险:多个标签页同时触发版本升级,容易卡住 - 没有跨上下文的“先读后写”保护机制,
get()和put()之间存在时间窗口
必须显式控制锁名、mode 和生命周期
锁名不是随便起的字符串,它是作用域标识符;mode 不传默认为 'shared',但写操作必须用 'exclusive',否则多个标签页能同时进回调。
- 锁名要按业务维度隔离,例如
`draft-${docId}`,而不是笼统的'update-draft' - 务必显式写
mode: 'exclusive',旧版 Chrome 会静默 fallback 到 shared - 回调函数必须返回 Promise,否则锁在函数退出时立即释放——哪怕
put()还没提交 - 不要依赖锁自动释放:即使抛错,也要确保
tx.done被 await,或用finally做兜底
正确封装:锁 + 事务 + done 等待缺一不可
常见错误是只锁了 openDB(),或者忘了 await tx.done。真正需要串行化的,是「读旧值 → 计算新值 → 写入」这个完整逻辑段。
const writeWithLock = async (docId, newData) => {
return navigator.locks.request(`draft-${docId}`, { mode: 'exclusive' }, async (lock) => {
const db = await openDB(); // 自定义的 indexedDB.open 封装
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
const old = await store.get(docId);
const merged = { ...old, ...newData, updatedAt: Date.now() };
await store.put(merged);
await tx.done; // 关键:必须 await,否则锁可能在写入完成前释放
});
};
- 不加
ifAvailable: true时,锁不可用会一直等待,UI 可能卡死;高交互场景建议加超时或降级提示 - 别在锁回调里做耗时操作(如压缩图片、解析大 JSON),只留最短必要路径
- Safari 完全不支持
navigator.locks(截至 iOS 17.5 / macOS 14.5),必须搭配服务端幂等写入或 localStorage + storage 事件模拟
它不解决什么,以及最容易被忽略的点
Web Locks API 不提供超时自动释放、不支持嵌套锁、不跨 origin、也不管 localStorage —— 它只在同源多标签页间做轻量互斥。最容易被忽略的是:锁名设计和 tx.done 的等待时机。一个拼错的 docId 或漏掉的 await,会让整套机制退化成裸写。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










