登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

前端 Web Locks 怎样查询当前持有者

来源:17golang原创

时间:2026-10-20 03:06:46 284浏览 收藏

多个标签页同时同步数据时,最难排查的往往不是“有没有调用 navigator.locks.request()”,而是当前到底谁拿到了锁、哪些请求还在排队。Web Locks 提供了 navigator.locks.query(),它返回当前 origin 的诊断快照:held 是已持有的锁,pending 是等待中的请求。它只读状态,不会抢锁、释放锁或取消等待。

官方地址:https://developer.mozilla.org/en-US/docs/Web/API/LockManager/query

排查“同步任务卡住”时,先看 held 里对应的 name、mode 和 clientId;再看 pending 是否有同名请求。不要把 pending 中的上下文当成当前持有者。

先确认触发信号与查询边界

如果页面表现为重复同步、某个任务迟迟没有进入回调,或者多个标签页像是在互相等待,可以先把问题收窄到“锁状态快照”。Web Locks 按 origin 隔离,来自不同协议、域名或端口的页面不会共享同一个锁管理器。

查询依赖安全上下文,并且页面需要能够取得 navigator.locks。因此排障记录里要同时写下页面 origin、是否 HTTPS、运行环境是标签页还是 Worker。下面的代码只做能力判断,不会创建锁:

function canInspectLocks() {
  // Web Locks 需要安全上下文,先避免在不支持的环境中直接访问。
  return window.isSecureContext && "locks" in navigator;
}

if (!canInspectLocks()) {
  console.warn("当前环境无法读取 Web Locks 状态");
}

这里的“无法读取”可能来自不安全上下文,也可能来自浏览器或运行环境没有实现 API。它不是业务锁被占用的证据。

读取 held 与 pending 两组锁信息

query() 不接收参数,返回一个 Promise。快照中的每个锁记录通常包含三个对排障最有用的字段:name 是请求时的锁名,mode 是 exclusive 或 shared,clientId 是发起请求的执行上下文标识。

Web Locks query 返回 held 与 pending 快照的结构说明图
图1:Web Locks 查询快照结构,held 表示已持有锁,pending 表示等待中的请求。
async function printLockSnapshot() {
  // query() 只读当前 origin 的锁管理器快照,不会修改锁状态。
  const snapshot = await navigator.locks.query();

  // held 是当前已经授予的锁;同名 shared 锁可能同时出现多个记录。
  for (const lock of snapshot.held) {
    console.log("held", {
      name: lock.name,
      mode: lock.mode,
      clientId: lock.clientId
    });
  }

  // pending 是尚未授予的请求,不能当作当前持有者。
  for (const request of snapshot.pending) {
    console.log("pending", {
      name: request.name,
      mode: request.mode,
      clientId: request.clientId
    });
  }

  return snapshot;
}

为了让值班记录可读,可以把输出按“持有区”和“等待区”分开保存。没有任何锁时,两个数组都可能为空;这只能说明查询时刻没有可见的持有或等待记录,不代表稍后不会有新请求。

根据 clientId 判断当前持有者

判断“谁持有锁”时,先在 held 中按 name 过滤,再记录同一条记录的 mode 与 clientId。pending 只说明有请求排队,它的 clientId 可以帮助定位等待方,但不能证明该上下文已经执行了临界区代码。

标签页和 Worker 通过 Web Locks 形成 held 与 pending 诊断边界的结构图
图2:锁持有者诊断边界,clientId 只标识请求上下文,不能被解读为业务用户身份。
function summarizeLock(snapshot, targetName) {
  // 只从 held 中寻找真正已授予的目标锁,避免把等待请求算成持有者。
  const holders = snapshot.held
    .filter((lock) => lock.name === targetName)
    .map((lock) => ({
      mode: lock.mode,
      clientId: lock.clientId
    }));

  // pending 用于解释“为什么还有请求没进去”,单独保留等待上下文。
  const waiters = snapshot.pending
    .filter((lock) => lock.name === targetName)
    .map((lock) => ({
      mode: lock.mode,
      clientId: lock.clientId
    }));

  return { targetName, holders, waiters };
}

这里的 clientId 是浏览器为执行上下文提供的标识,不应直接当作用户 ID、标签页标题或业务会话名。若需要把它映射到业务日志,应在应用自己的请求上下文中另行记录,不要猜测 Web Locks 字段的含义。

处理查询失败、空快照与回滚路径

查询本身可能返回拒绝的 Promise。官方接口列出的常见异常包括文档尚未 fully active 时的 InvalidStateError,以及无法取得锁管理器时的 SecurityError。运行手册中应把这些情况和“锁确实被别的标签页持有”分开。

async function inspectLocksSafely() {
  try {
    // 只在能力检查通过后调用 query(),将异常转成可追踪的诊断结果。
    const snapshot = await navigator.locks.query();
    return {
      ok: true,
      held: snapshot.held,
      pending: snapshot.pending
    };
  } catch (error) {
    // 保留异常类型,便于区分上下文问题与业务锁等待。
    return {
      ok: false,
      errorName: error.name,
      message: error.message
    };
  }
}

如果结果是空快照,复盘时记录查询时间和 origin;如果结果是异常,记录 errorName 和页面生命周期状态。回滚路径也很明确:查询函数只撤回诊断日志,不对现有锁做强制操作;真正的锁释放仍由持锁请求的异步回调结束来完成。

告警确认与复盘项

线上告警可以把“同名锁持续出现在 held”“pending 数量持续增长”“query() 连续失败”作为不同信号,但不要只凭一次快照下结论。对每次采样保存 name、mode、clientId、origin 和采样时间,之后再和同步任务日志关联。

复盘时重点问三件事:锁名是否过于宽泛,导致无关任务排队;持锁回调是否包含了不必要的慢操作;等待方是否有超时或用户可见的降级路径。query() 能告诉你“当前看见了什么”,但不能替应用决定锁应该由谁释放。

常见问题

query() 能直接查某一个锁名吗?

不能。query() 没有参数,会返回快照;需要查询某个名字时,在返回的 held 和 pending 数组上用 name 过滤。

pending 里的 clientId 是当前持有者吗?

不是。它代表发起等待请求的执行上下文。当前持有者应从同名锁的 held 记录中判断。

调用 query() 会让等待请求立即获得锁吗?

不会。它是诊断查询,不改变授予、排队或释放状态。

把 held 当作当前授予结果,把 pending 当作等待记录,再用 clientId 关联执行上下文,就能把“谁在持锁”从猜测变成可复查的状态快照。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>