Skip to content

如何优雅处理多个异步操作?

约 1045 字大约 3 分钟

JavaScript

2025-04-01

在 JavaScript 开发中,经常会遇到需要处理多个异步操作的需要,为避免陷入回调地狱或逻辑混乱,总结了处理多异步任务的方法,包含 Promiseasync/await 及常见陷阱解决方案。

1、传统异步问题:回调地狱

// 嵌套地狱示例
fetchUser(user => {
  fetchOrders(user.id, orders => {
    orders.forEach(order => {
      fetchDetails(order.id, details => {
        // ... 逻辑层层嵌套
      });
    });
  });
});

这种代码难以维护,而现代 JavaScript 提供了更优雅的解决方案。


2. Promise 链式调用

2.1. 基本链式操作

fetchUser()
  .then(user => fetchOrders(user.id))
  .then(orders => Promise.all(orders.map(order => fetchDetails(order.id))))
  .then(allDetails => console.log("所有订单详情:", allDetails))
  .catch(error => console.error("链式调用失败:", error));

2.2. 关键特性

  • 错误冒泡:链中任一环节的错误会直接跳到最近的 catch()
  • 扁平化结构:避免嵌套,通过 return 传递结果

3. 并行处理

3.1. Promise.all

全成功才成功。

const [user, posts, comments] = await Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
]);

// 所有请求完成后统一处理
console.log("用户:", user);
console.log("文章:", posts);
console.log("评论:", comments);

适用场景:

  • 所有任务相互独立且必须全部完成
  • 需要聚合多个接口数据后再渲染页面

3.2. Promise.allSettled

无论成功与否都等待。

const results = await Promise.allSettled([
  fetchUser(),
  fetchPosts().then(() => { throw new Error("模拟失败") }),
  fetchComments()
]);

results.forEach(result => {
  if (result.status === "fulfilled") {
    console.log("成功结果:", result.value);
  } else {
    console.error("失败原因:", result.reason);
  }
});

适用场景:

  • 需要处理部分成功/失败的情况(如表单多步骤提交)
  • 监控多个独立任务的最终状态

4. 竞速处理

4.1. Promise.race

以第一个完成为准。

// 超时控制示例
const fetchWithTimeout = Promise.race([
  fetchData(),
  new Promise((_, reject) => setTimeout(() => reject("请求超时"), 3000))
]);

fetchWithTimeout
  .then(data => console.log("成功获取数据"))
  .catch(error => console.error(error)); // 3秒内未完成则触发超时

4.2. Promise.any

以第一个成功为准(ES2021)。

// 多 CDN 备用请求
const cdnProviders = [
  fetchFromCDN1(),
  fetchFromCDN2(),
  fetchFromCDN3()
];

Promise.any(cdnProviders)
  .then(resource => console.log("成功加载资源"))
  .catch(error => console.error("所有 CDN 均失败"));

5. 错误处理策略

5.1. 集中式错误处理

async function handleData() {
  try {
    const data = await fetchData();
    const processed = await processData(data);
    await saveData(processed);
  } catch (error) {
    // 统一处理所有错误
    handleError(error);
  }
}

5.2. 选择性错误捕获

// 捕获特定错误
try {
  await riskyOperation();
} catch (error) {
  if (error.code === 404) {
    handleNotFound();
  } else if (error.code === 401) {
    redirectToLogin();
  } else {
    throw error; // 重新抛出未知错误
  }
}

6. 进阶技巧:异步控制流

6.1. 限制并发数

// 并发控制函数(最多同时3个请求)
async function processInBatches(tasks, concurrency) {
  const results = [];
  for (let i = 0; i < tasks.length; i += concurrency) {
    const batch = tasks.slice(i, i + concurrency);
    results.push(...await Promise.all(batch.map(task => task())));
  }
  return results;
}

// 使用示例
const tasks = [...Array(10).keys()].map(i => () => fetchData(i));
processInBatches(tasks, 3);

6.2. 串行执行

async function processSerially(tasks) {
  const results = [];
  for (const task of tasks) {
    results.push(await task());
  }
  return results;
}

// 使用示例
processSerially([
  () => step1(),
  () => step2(),
  () => step3()
]);

7. 常见陷阱与解决方案

7.1. 遗漏错误处理

// ❌ 错误写法:未捕获错误
Promise.all([fetch1(), fetch2()])
  .then(() => console.log("成功"));

// ✅ 正确写法:始终添加 catch
Promise.all([...])
  .then(...)
  .catch(error => {
    // 处理错误或记录日志
  });

7.2. 忽略清理操作

// 使用 AbortController 中止请求(Fetch API)
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

fetch(url, { signal: controller.signal })
  .catch(err => {
    if (err.name === "AbortError") {
      console.log("请求已中止");
    }
  });

8. 代码结构优化建议

8.1. 分离业务逻辑与异步逻辑

8.2. 使用 async/await 简化代码

// ❌ 嵌套写法
fetchUser(user => {
  fetchOrders(user.id, orders => {
    // ...
  });
});

// ✅ 扁平化写法
async function loadUserData() {
  const user = await fetchUser();
  const orders = await fetchOrders(user.id);
  // ...
}

9. 性能优化建议

  1. 预加载数据:在空闲时间预取可能用到的数据
  2. 缓存策略:对重复请求进行内存或本地存储缓存
  3. 请求合并:将多个小请求合并为一个批量请求
  4. 优先级调度:对关键任务设置更高的优先级

10. 总结

掌握多异步操作处理的关键在于:

  1. 根据场景选择合适的 API(all/race/any
  2. 建立清晰的错误处理机制
  3. 保持代码结构的简洁性