主题
Web Locks(navigator.locks)
同源下跨标签页 / Worker 的互斥协调 API。用于同一时刻只允许一个上下文占用某共享资源,避免多页同时改同一状态。
入口:navigator.locks(LockManager)。需要安全上下文(HTTPS);Worker 中也可用。
适用场景
- 多标签页同时对同一资源做写操作(大文件断点续传、IndexedDB / localStorage 写入)
- 文档编辑器:同一文档在多个标签页打开时的编辑权
- 单页内串行化一批异步任务(批量上传、串联请求)
为何不用常见跨页方案
| 方案 | 主要问题 |
|---|---|
localStorage + 轮询 | 读改写非原子,易竞态;崩溃后锁可能残留;需轮询 |
| Broadcast Channel | 需自建心跳、崩溃检测与冲突解决,无法原子抢占 |
Web Locks 由浏览器管理加锁 / 排队 / 释放,回调结束(含 async 完成或抛错)后自动释放。
request
js
request(name, callback)
request(name, options, callback)name:锁名,同名锁在同源内共享调度callback:获锁后执行;通常为async,锁持有到该函数完全结束- 返回值:
Promise,在锁释放后以 callback 的返回值 settle
js
await navigator.locks.request('upload:file-id', async (lock) => {
// 持有锁期间执行业务
})options
| 选项 | 说明 |
|---|---|
mode | "exclusive"(默认)或 "shared"。排他锁同时只能有一个持有者;共享锁可并存,但不能与排他锁并存 |
ifAvailable | true 时仅在可立即获锁时授予;否则 callback 收到 null,不排队等待。默认 false |
steal | true 时强制释放同名已持有锁并授予当前请求。原持有方代码仍可能继续跑,易冲突,慎用 |
signal | AbortSignal;请求尚未授予时可中止 |
js
// 抢不到就跳过(多标签页上传:仅一个页真正上传)
await navigator.locks.request('upload:file-id', { ifAvailable: true }, async (lock) => {
if (!lock) return
await upload()
})读写锁模式:
js
// 多读
await navigator.locks.request('doc', { mode: 'shared' }, async () => {
/* read */
})
// 单写
await navigator.locks.request('doc', { mode: 'exclusive' }, async () => {
/* write */
})持有时长
锁在 callback 返回后释放。长任务应在 callback 内 await 到真正结束,而不是 fire-and-forget:
js
await navigator.locks.request(LOCK_NAME, { ifAvailable: true }, async (lock) => {
if (!lock) return
await runUntilComplete() // 完成后才释放
})标签页崩溃时,浏览器会释放该页持有的锁,无需手动清理残留标记。
query
js
const state = await navigator.locks.query()
// state.held / state.pending:当前已持有与排队中的锁信息调试或观测用;业务路径优先依赖 request 的调度结果。
边界
- 作用域是同源,不是跨站
- 默认
exclusive;多读者场景才用shared ifAvailable: true时务必判断lock === nullsteal不中断旧回调,只抢锁名,不适合作为常规冲突策略
