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

Atomics.waitAsync 主线程等待共享内存通知的实现方式与超时边界

来源:17golang原创

时间:2026-11-09 11:36:50 110浏览 收藏

如果页面需要等待共享内存中的状态变化,Atomics.waitAsync() 的关键价值是“等待期间不阻塞整条主线程”。它只能接收指向 SharedArrayBuffer 的 Int32Array 或 BigInt64Array,返回值还分成同步字符串和异步 Promise 两种形态:初始值不匹配时立即得到 not-equal,零超时可能立即得到 timed-out,真正进入等待后再由 Promise 得到 ok 或 timed-out。

官方资料地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync

先把返回对象拆开:async 决定是否等待

Atomics.waitAsync(view, index, expected, timeout) 返回一个对象,而不是直接返回 Promise。先看 async,再处理 value,可以避免把同步字符串误当成 Promise。

状态典型返回含义
值已变化{ async: false, value: "not-equal" }不用等待,当前值已经不是 expected
零超时{ async: false, value: "timed-out" }只做一次即时检查
进入等待{ async: true, value: Promise }等待通知或超时,Promise 最终为 ok 或 timed-out
Atomics.waitAsync 返回对象中 async 与 value 两条结果路径的静态结构说明图
图1:Atomics.waitAsync 返回值说明图,展示同步字符串、异步 Promise 与最终状态的关系;这不是运行截图。

用共享整数建立一个主线程等待点

下面的例子用一个共享整数表示“是否就绪”。主线程调用等待后仍能处理定时器和界面事件;示例中的定时器只模拟另一个执行者写入状态并通知等待者。

// main.js:用共享整数表示 ready 状态,0 表示未就绪,1 表示已就绪
const shared = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
const state = new Int32Array(shared);

async function waitUntilReady(timeout = 1000) {
  // 第三个参数是期望值;若它已变化,方法会立即返回 not-equal
  const result = Atomics.waitAsync(state, 0, 0, timeout);

  // value 可能是字符串,也可能是 Promise,统一转换后再处理结果
  return result.async ? await result.value : result.value;
}

waitUntilReady().then((status) => {
  // ok 表示收到通知,timed-out 表示在期限内没有收到通知
  console.log("共享状态等待结果:", status);
});

setTimeout(() => {
  // 先写入新状态,再通知等待者,减少读到旧状态的竞态
  Atomics.store(state, 0, 1);
  Atomics.notify(state, 0, 1);
}, 100);

这个示例的可复用边界有三点:视图必须指向共享内存,等待位置必须在数组范围内,写入方要先完成 Atomics.store 再执行 Atomics.notify。如果调用方本来就把位置改成了 1,等待函数不会再挂起,而是直接返回 not-equal。

超时和通知竞态要按结果处理

超时不是异常。异步等待的 Promise 会以字符串完成,不会因为等不到通知而自动 reject,所以业务代码应显式分支。通知也不是“把值改好”的动作,真正改变共享状态的是写入操作;只调用 Atomics.notify 而不更新值,下一次检查仍可能看到旧状态。

// 只等待一次:把超时当作正常业务分支,不制造未处理拒绝
const result = Atomics.waitAsync(state, 0, 0, 250);
const status = result.async ? await result.value : result.value;

if (status === "ok") {
  // 收到通知后仍应读取共享值,确认状态确实满足业务条件
  const ready = Atomics.load(state, 0) === 1;
  console.log("收到通知,ready=", ready);
} else if (status === "timed-out") {
  // 超时只说明本轮没有等到通知,不等于共享数据损坏
  console.log("等待超时,保留重试或降级路径");
} else {
  // not-equal 表示调用前状态已经变化,可直接进入检查流程
  console.log("状态已提前变化:", status);
}
Atomics.store、Atomics.notify、等待结果和超时分支之间的静态边界说明图
图2:通知与超时边界说明图,强调先写共享值、再通知,以及 ok、timed-out、not-equal 三种结果的处理关系;这不是运行截图。

兼容性与降级:不要把新 API 当成唯一通道

使用前先做能力检测,并把共享内存的安全上下文限制纳入部署检查。浏览器不支持 Atomics.waitAsync,或页面无法获得可用的 SharedArrayBuffer 时,可以退回 Worker 消息、普通事件或轮询任务;降级方案虽然没有共享内存的同步语义,但更容易覆盖旧环境。

// 先检测 API 和共享内存构造能力,再选择等待实现
function canUseWaitAsync() {
  try {
    return typeof Atomics.waitAsync === "function" &&
      typeof SharedArrayBuffer === "function";
  } catch (error) {
    // 某些受限环境访问构造器会失败,交给上层走消息降级
    return false;
  }
}

if (!canUseWaitAsync()) {
  // 这里应接入 Worker.postMessage、事件或业务已有的异步通知机制
  console.log("使用普通异步通知降级");
}
  • 需要主线程继续响应交互时,优先使用 waitAsync,不要用会阻塞线程的同步等待。
  • 看到 not-equal 先读取当前共享值,不要把它记录成超时。
  • 发布前用目标浏览器做特性检测;安全上下文或跨源隔离条件不满足时,保留消息型降级。

常见问题

Atomics.waitAsync 和 Atomics.wait 最大的区别是什么?

Atomics.wait 会同步等待,而 waitAsync 返回可等待的 Promise,不会把主线程整个卡住。两者都依赖共享内存视图和正确的通知顺序。

为什么 timeout 设为 0 时没有 Promise?

零超时只做即时比较,不进入等待队列,因此可以直接返回 timed-out 或 not-equal。代码应先判断 result.async,不要无条件调用 result.value.then()。

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