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

Web Locks API 的等待请求如何支持用户主动取消

来源:17golang原创

时间:2026-10-09 21:29:00 239浏览 收藏

Web Locks API 的等待请求可以主动取消,但取消点只在“锁还没有授予”这一段。做法是在 navigator.locks.request() 的 options 中传入 AbortController.signal,用户点击取消或等待超时时调用 abort(),再把 AbortError 当作正常的放弃等待处理。若回调已经开始,锁会保持到回调返回;这时要另行取消业务任务,不能指望 request 的 signal 强行打断临界区。

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

把 Web Locks 的控制拆成两层:request signal 负责“还没拿到锁时退出队列”,业务 AbortController 负责“拿到锁后停止具体工作”。
要点速览
  • signal 取消的是未获锁的排队请求,拒绝结果通常是 AbortError。
  • 回调一旦开始,锁的释放由回调返回决定,不能用同一个 signal 伪造强制解锁。
  • 用户取消、等待超时、业务 fetch 中断和 finally 清理应分别建模。

AbortSignal 只取消排队中的请求

同一个 origin 下的多个标签页或 Worker 可能竞争同名锁。默认的 exclusive 锁在前一个回调完成前会让后续请求排队,因此“取消等待”本质上是从队列中撤下尚未授予的请求。MDN 对 signal 的定义也明确指出:控制器中止时,如果请求还没有获授,锁请求会被丢弃。

async function waitForSyncLock(signal) {
  try {
    return await navigator.locks.request(
      "document-sync",
      { mode: "exclusive", signal },
      async (lock) => {
        // 只有拿到锁后才进入互斥工作区。
        await flushPendingChanges();
        return lock?.name;
      },
    );
  } catch (error) {
    // AbortError 表示放弃排队,不应当当成业务故障重试。
    if (error instanceof DOMException && error.name === "AbortError") {
      return "cancelled-before-grant";
    }
    throw error;
  }
}
Web Locks API 排队请求、已持有锁与 AbortSignal 取消边界说明图
图1:Web Locks 排队取消说明图,取消只作用于尚未获锁的请求。

这里的返回值来自回调,真正的锁释放仍由异步回调结束决定。代码中不要把 AbortError 记录成“锁损坏”;它表示当前调用者选择不再等待。

用 request 的 signal 配合超时与用户取消

等待超时和用户点击取消可以共享一个“等待控制器”,但业务处理最好使用另一个控制器。这样即使锁在取消信号到达前恰好授予,回调也能根据业务信号停止网络或计算,并在 finally 中完成清理。

async function syncWithCancel(userSignal) {
  const waitController = new AbortController();
  const workController = new AbortController();
  const timeoutId = setTimeout(() => waitController.abort(), 3000);

  // 用户取消同时终止等待和已开始的业务任务。
  userSignal.addEventListener("abort", () => {
    waitController.abort();
    workController.abort();
  }, { once: true });

  try {
    return await navigator.locks.request(
      "document-sync",
      { signal: waitController.signal },
      async () => {
        // 锁已授予:后续任务由 workController 自己负责取消。
        return await fetch("/api/sync", { signal: workController.signal });
      },
    );
  } finally {
    // 无论等待失败还是回调完成,都要清掉定时器和监听副作用。
    clearTimeout(timeoutId);
  }
}
Web Locks API 等待阶段与执行阶段的双 AbortController 生命周期结构图
图2:Web Locks 生命周期结构图,等待取消与临界区任务取消各自负责不同边界。

不要把取消等待误当成取消临界区

状态推荐控制结果
请求仍在队列request 的 signalPromise 以 AbortError 拒绝,回调不会启动
回调已经启动业务专用 AbortController停止 fetch 或计算,等待回调自行收尾
回调即将结束finally 清理释放临时资源,锁随回调返回自动释放

不要为了“尽快让下一个标签页拿到锁”而使用 steal 作为取消方案。它会抢占同名已持有锁,但原先正在执行的代码仍可能继续运行,反而制造并发写入。需要快速失败时,排队阶段可以使用 ifAvailable;需要可取消等待时,使用 signal 更容易表达真实意图。

上线前的 Web Locks 取消检查清单

  • 锁名是否代表真实共享资源,并且所有标签页使用同一命名规则?
  • 是否只在等待阶段把 AbortError 当作可预期分支?
  • 回调中的 fetch、定时器、流读取是否有独立的业务取消信号?
  • 回调的 finally 是否会释放监听器、临时状态和 UI loading?
  • 是否在 HTTPS 安全上下文和目标浏览器中确认了 Web Locks API 可用性?

常见问题

AbortController.abort() 后为什么回调仍然执行?

通常是因为锁已经在 abort 发生前授予。request 的 signal 不能撤销已经开始的回调,应检查业务信号并让回调尽快返回。

Web Locks 的 signal 可以和 ifAvailable 一起用吗?

不能简单叠加。规范和 MDN 将 signal 与 ifAvailable 或 steal 的组合列为不支持的参数组合;需要快速失败就选 ifAvailable,需要可取消排队就选 signal。

取消等待后要不要立刻重试?

不要把用户取消当成无限重试信号。只有在明确的业务策略允许时,才用退避和新的 AbortController 创建下一次请求。

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