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 |

用共享整数建立一个主线程等待点
下面的例子用一个共享整数表示“是否就绪”。主线程调用等待后仍能处理定时器和界面事件;示例中的定时器只模拟另一个执行者写入状态并通知等待者。
// 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);
}

兼容性与降级:不要把新 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()。
-
130 收藏
-
306 收藏
-
194 收藏
-
414 收藏
-
427 收藏
-
400 收藏
-
163 收藏
-
文章 · 前端 | 35分钟前 | 前端 · 性能 · javascript · ReadableStream Web Streams highWaterMark desiredSize backpressure 前端流式读取239 收藏
-
183 收藏
-
157 收藏
-
489 收藏
-
157 收藏
-
110 收藏
-
320 收藏
-
文章 · 前端 | 1天前 | javascript · css · View Transition API view-transition-name Element.startViewTransition409 收藏
-
303 收藏
-
293 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习