异步并发控制

在 JavaScript 异步编程中,并发控制是处理多任务协同执行的核心能力。本节通过"多 API 请求容错"和"跨线程同步优化"两个场景,探讨 Promise 并发处理与现代线程通信技术的实践应用。

多 API 请求的容错处理

当需要并行发起多个 API 请求且需确保所有请求完成后统一处理(无论成功或失败)时,Promise.allSettled 提供了比 Promise.all 更灵活的容错机制。与 Promise.all 在任一请求失败时立即拒绝不同,Promise.allSettled 会等待所有请求完成,并返回包含每个请求状态("fulfilled" 或 "rejected")和结果的对象数组,这使得开发者能够全面捕获成功数据与失败原因,实现精细化错误处理。

使用范式:通过 Promise.allSettled 获取所有请求结果后,可通过状态过滤分离成功与失败数据,典型处理流程包括:

  1. 发起并行请求并等待全部完成
  2. 过滤出状态为 "fulfilled" 的成功结果
  3. 提取有效数据进行业务处理
  4. (可选)收集失败信息用于监控或重试

代码实现示例

javascript
复制代码
// 模拟多个 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 中的无阻塞跨线程同步

Web Worker 作为浏览器端的多线程解决方案,允许将 CPU 密集型任务(如图像处理、大数据计算)移至后台线程执行,通过 postMessage 实现主线程与工作线程的消息传递。然而,传统基于轮询的共享内存监控会导致不必要的性能开销,而 ES2024 引入的 Atomics.waitAsync 提供了非阻塞的内存变化监听机制,显著优化了跨线程同步效率。

传统轮询方式的局限
在共享内存通信中,工作线程需持续检查特定内存位置的值变化以触发后续操作。传统实现采用 while 循环结合 setTimeoutPromise.resolve() 的轮询模式,即使在等待期间也会频繁占用线程资源,导致 CPU 利用率升高和能源浪费:

javascript
复制代码
// 传统轮询实现(低效)
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 具有三个核心改进:

  • 零 CPU 占用:等待期间线程处于闲置状态,不消耗计算资源
  • 即时响应:内存值变化时立即触发回调,无轮询延迟
  • 简洁语法:通过 Promise 链式调用简化异步逻辑,避免嵌套循环

跨线程同步实现示例

主线程代码

javascript
复制代码
// 创建 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)

javascript
复制代码
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 并发方法、原子操作、线程通信的综合体系,开发者需根据任务特性(如是否需要容错、是否跨线程)选择最优技术路径,以实现性能与可靠性的平衡。