Web Locks API 无法协调 localStorage 并发写入,因后者是同步阻塞 API,锁无法插入执行流;必须改用 IndexedDB + locks 实现真正串行化写入。

Web Locks API 无法协调 localStorage 的并发写入,加锁后依然会覆盖——因为 localStorage 是同步阻塞 API,锁根本插不进执行流里。
为什么 navigator.locks.request 对 localStorage 写入完全无效
很多人试过这样写:
await navigator.locks.request('cart-lock', () => {
localStorage.setItem('cart', JSON.stringify(newCart));
});
结果发现两个标签页还是同时写、互相覆盖。原因很直接:
-
localStorage.setItem是同步立即执行的,不等 Promise、不等回调返回、不等锁释放 -
navigator.locks.request是 Promise-based 异步机制,它只能包裹异步操作(如fetch、indexedDB),对同步调用零干预能力 - 即使锁名一致,多个标签页仍可能在毫秒级间隔内先后进入回调并执行
setItem,锁只保证“进入回调”的顺序,不保证“写入完成”的顺序
真正能串行化写入的替代方案只有 IndexedDB + locks
如果你需要多标签页安全更新同一份结构化数据(比如用户偏好、购物车、计数器),必须放弃 localStorage,改用支持事务和异步的 IndexedDB:
- 用
indexedDB.open()打开数据库,事务模式设为'readwrite',天然提供单次写入原子性 - 把「读当前值 → 计算新值 →
put()写回」整个流程包进navigator.locks.request回调,并await事务完成(如tx.done) - 锁名必须带业务标识,例如
'user-preferences-write',避免不同模块误共享同一把锁
示例关键段:
const updateTheme = async (theme) => {
await navigator.locks.request('user-preferences-write', async () => {
const db = await openDB('myapp', 1);
const tx = db.transaction('settings', 'readwrite');
const store = tx.objectStore('settings');
const current = await store.get('theme') || { value: 'light' };
await store.put({ key: 'theme', value: theme });
await tx.done; // 必须 await,否则锁提前释放
});
};
如果非要用 localStorage,唯一可行的“模拟协调”只是提示,不是保护
你可以用 localStorage 自身 + storage 事件做轻量提示,但它不防止覆盖,只用于友好反馈:
- 写前先存一个带时间戳的临时标记:
localStorage.setItem('lock:cart', Date.now().toString()) - 监听
storage事件,若检测到其他标签页也写了lock:cart,就弹提示“另一标签页正在编辑” - 这种方案没有互斥力,两个标签页仍可同时写;它只是把竞态从“静默覆盖”变成“用户可见冲突”
别把它当成锁的降级方案——它连降级都算不上,只是 UI 层的补救。
真正的难点从来不在怎么调用 navigator.locks.request,而在于识别哪些状态值得升级到 IndexedDB、锁粒度是否匹配业务变更边界、以及当锁请求失败(如超时或被中断)时,用户流程是否还能降级继续——这些决策,API 不会替你做。

















