navigator.locks不能用于同步权限,仅限同源标签页互斥;必须将资格判断、资源获取与使用全链路纳入锁中,锁名须与资源粒度严格对齐且url-safe,mode和ifavailable需显式设置,回调须返回promise,锁内逻辑须轻量原子,safari不支持需降级,服务端幂等是底线。

不能靠 navigator.locks “同步权限”,它只保证同一时间至多一个标签页执行某段逻辑,且仅限同源;真正要协调对唯一系统资源的独占访问,必须把“判断资格 + 获取资源 + 使用资源”整条链路包进锁里,否则权限检查和实际使用之间仍有竞态窗口。
锁名必须与资源粒度严格对齐
锁名不是标签或备注,它是资源作用域的唯一标识符。用固定字符串(如 'system-resource')会导致所有用户、所有操作串行化——一人调用,全站阻塞。
- 错:
navigator.locks.request('system-resource', ...)—— 所有请求抢同一把锁 - 对:
navigator.locks.request(`system-resource-${resourceId}`, ...)或`user-${userId}-system-resource` - 锁名需 URL-safe:避免空格、斜杠、控制字符;长度建议 ≤64 字符,过长可能影响内部哈希性能
- 别用时间戳或随机数拼锁名,否则锁失效——不同标签页拿不到同一把锁
mode 和 ifAvailable 必须显式设置
不传 mode 时,旧版 Chrome 会静默 fallback 到 'shared',而 shared 锁无法阻止并发写入;不设 ifAvailable: true,锁不可用时 UI 会无限等待、彻底卡死。
- 写操作一律用
mode: 'exclusive',读操作才考虑shared - 高交互场景务必启用
ifAvailable: true,避免阻塞主线程 - 可配合
AbortSignal加超时:{ ifAvailable: true, signal: AbortSignal.timeout(3000) } - 回调函数必须返回 Promise,否则锁在函数退出后立即释放——
return fetch(...)✅,return 'done'❌
锁内只能包原子段,不能包副作用
锁持有时间越长,排队越严重,且 Safari 完全不支持该 API(截至 iOS 17.5 / macOS 14.5),所以锁内逻辑必须轻量、确定、快速完成。
- 只包「检查资源状态 → 请求/获取资源 → 更新本地记录」这一小段,比如调用
fetch('/api/resource/claim')并解析响应 - 上传文件、渲染 UI、加密计算、日志上报等全部移出锁外
- 若资源获取本身是异步的(如服务端分配 token),锁应延续到 token 确认生效后才释放
- 别在锁里做重试逻辑——失败就释放锁,让上层决定是否重试
Safari 不支持是硬伤,降级方案不能省
仅靠 if ('locks' in navigator) 跳过逻辑,等于在 Safari 里彻底放弃独占保障。必须为无锁环境设计兜底路径。
- 服务端幂等是底线:每个 claim 请求附带客户端生成的
requestId,后端校验是否已分配过该资源 - 前端轻量提示可用
localStorage+storage事件模拟:写前存lock:resource-123,监听到冲突则提示“其他窗口正在占用”,但这不是强一致 - 切勿用
localStorage.setItem替代锁——它同步、无事务、无法被navigator.locks协调,只会掩盖问题 - 真正难的从来不是加锁动作本身,而是判断:这个资源的“独占性”到底该由前端强制 enforce,还是交由服务端仲裁更合理?很多场景下,后端带版本号(如
_rev)的乐观锁,比前端死磕互斥更可靠。










