在 JavaScript 异步编程中,并发控制是处理多任务协同执行的核心能力。本节通过"多 API 请求容错"和"跨线程同步优化"两个场景,探讨 Promise 并发处理与现代线程通信技术的实践应用。
当需要并行发起多个 API 请求且需确保所有请求完成后统一处理(无论成功或失败)时,Promise.allSettled 提供了比 Promise.all 更灵活的容错机制。与 Promise.all 在任一请求失败时立即拒绝不同,Promise.allSettled 会等待所有请求完成,并返回包含每个请求状态("fulfilled" 或 "rejected")和结果的对象数组,这使得开发者能够全面捕获成功数据与失败原因,实现精细化错误处理。
使用范式:通过 Promise.allSettled 获取所有请求结果后,可通过状态过滤分离成功与失败数据,典型处理流程包括:
代码实现示例:
// 模拟多个 API 请求
const fetchUsers = fetch('https://api.example.com/users').then(res => res.json());
const fetchProducts = fetch('https://api.example.com/products').then(res => res.json());
const fetchOrders = fetch('https://api.example.com/orders/invalid').then(res => res.json()); // 模拟失败请求
// 等待所有请求完成并处理结果
Promise.allSettled([fetchUsers, fetchProducts, fetchOrders])
.then(results => {
// 过滤成功项并提取数据
const successfulData = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
// 过滤失败项并收集错误信息
const errors = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
console.log('成功数据:', successfulData);
console.log('失败信息:', errors);
});
此模式特别适用于数据仪表盘、批量导入等需完整呈现部分成功结果的场景,避免因单个请求失败导致整体功能不可用。
Web Worker 作为浏览器端的多线程解决方案,允许将 CPU 密集型任务(如图像处理、大数据计算)移至后台线程执行,通过 postMessage 实现主线程与工作线程的消息传递。然而,传统基于轮询的共享内存监控会导致不必要的性能开销,而 ES2024 引入的 Atomics.waitAsync 提供了非阻塞的内存变化监听机制,显著优化了跨线程同步效率。
传统轮询方式的局限:
在共享内存通信中,工作线程需持续检查特定内存位置的值变化以触发后续操作。传统实现采用 while 循环结合 setTimeout 或 Promise.resolve() 的轮询模式,即使在等待期间也会频繁占用线程资源,导致 CPU 利用率升高和能源浪费:
// 传统轮询实现(低效)
async function pollSharedMemory(sharedArray) {
while (Atomics.load(sharedArray, 0) === 0) {
await new Promise(resolve => setTimeout(resolve, 50)); // 每 50ms 轮询一次
}
console.log('共享内存值已更新');
}
Atomics.waitAsync 的非阻塞优化:Atomics.waitAsync 是原子操作 API 的异步扩展,它允许线程在等待共享内存特定位置的值变化时释放执行权,避免无效循环。其返回一个包含 value 属性(Promise 实例)的对象,当内存值满足条件或等待超时后,Promise 会自动解析,实现真正的无阻塞等待:
技术优势:与传统轮询相比,Atomics.waitAsync 具有三个核心改进:
跨线程同步实现示例:
主线程代码:
// 创建 4 字节共享内存缓冲区(存储 Uint32 类型数据)
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Uint32Array(sharedBuffer);
// 初始化值为 0,发送共享内存给工作线程
const worker = new Worker('data-processor.js');
worker.postMessage({ sharedBuffer });
// 模拟 2 秒后更新共享内存值(触发工作线程响应)
setTimeout(() => {
Atomics.store(sharedArray, 0, 1); // 将索引 0 位置的值设为 1
Atomics.notify(sharedArray, 0); // 通知等待的工作线程
}, 2000);
工作线程代码(data-processor.js):
onmessage = async ({ data }) => {
const { sharedBuffer } = data;
const sharedArray = new Uint32Array(sharedBuffer);
// 异步等待共享内存值从 0 变为非 0
const waitResult = Atomics.waitAsync(sharedArray, 0, 0);
await waitResult.value; // 等待 Promise 解析
console.log('检测到共享内存更新,当前值:', Atomics.load(sharedArray, 0));
// 执行后续数据处理逻辑...
};
| 特性 | 传统轮询模式 | Atomics.waitAsync 模式 |
|---|---|---|
| CPU 占用 | 持续低负载(循环+延迟) | 零占用(等待时释放线程) |
| 响应延迟 | 取决于轮询间隔(如 50ms) | 即时(内存变化立即触发) |
| 代码复杂度 | 需手动控制循环与延迟 | 基于 Promise 链式调用 |
| 适用环境 | 兼容性要求低的旧项目 | 支持 ES2024 的现代浏览器 |
Atomics.waitAsync 特别适合高频跨线程数据同步场景,如实时数据流处理、多线程计算任务协同等。在 Web Worker 中结合共享内存与异步原子等待,可构建高效的多线程应用架构,同时保持主线程的流畅响应。
通过上述两种场景的实践可见,JavaScript 异步并发控制已从单一的 Promise 链式调用发展为包含Promise 并发方法、原子操作、线程通信的综合体系,开发者需根据任务特性(如是否需要容错、是否跨线程)选择最优技术路径,以实现性能与可靠性的平衡。