找回密码
 立即注册
首页 业界区 业界 JavaScript 异步编程指南:async/await 与 Promise 该怎 ...

JavaScript 异步编程指南:async/await 与 Promise 该怎么选?

匣卒 2025-7-14 11:48:36
在 JavaScript 开发中,异步操作就像家常便饭 —— 从调用后端 API 到读取本地文件,几乎无处不在。但很多开发者都会困惑:到底该用 Promise 的链式调用,还是 async/await 语法?其实答案很简单:没有绝对的好坏,只有场景的适配
今天我们就用实际案例聊聊,这两种异步写法各自适合什么场景,以及如何在项目中混搭使用,让代码既高效又易读。
先搞懂:两者不是对立关系
很多人以为 async/await 是 Promise 的替代品,其实大错特错。async/await 本质是 Promise 的语法糖,它的底层依然是 Promise 实现。就像用for...of遍历数组比forEach更直观一样,async/await 让异步代码看起来更像同步代码。
先看个最简单的对比:
  1. // Promise写法
  2. fetchData().then(data => {
  3.   return processData(data);
  4. }).then(result => {
  5.   console.log(result);
  6. }).catch(err => {
  7.   console.error(err);
  8. });
  9. // async/await写法
  10. async function handleData() {
  11.   try {
  12.     const data = await fetchData();
  13.     const result = await processData(data);
  14.     console.log(result);
  15.   } catch (err) {
  16.     console.error(err);
  17.   }
  18. }
复制代码
两者功能完全一致,但 async/await 的线性结构更符合人类的阅读习惯 —— 这也是它被广泛采用的核心原因。
优先用 async/await 的 3 种场景
什么时候用 async/await 更合适?记住一个原则:当异步操作需要按顺序执行,或者逻辑中有较多条件判断时
1. 线性异步流程(一步接一步)

最典型的场景是依赖前一步结果的异步操作。比如先登录获取 token,再用 token 获取用户信息,最后用用户信息加载权限配置:
  1. // 用async/await,逻辑一目了然
  2. async function initApp(username, password) {  // 补充参数定义,避免未定义变量
  3.   try {
  4.     const token = await login(username, password);
  5.     const userInfo = await getUserInfo(token);  // 依赖token
  6.     const permissions = await getPermissions(userInfo.role);  // 依赖userInfo
  7.     renderApp(permissions);
  8.   } catch (err) {
  9.     showError(err);
  10.   }
  11. }
复制代码
如果用 Promise 链式调用写,虽然能实现,但嵌套越深(比如再加两步),可读性会明显下降。
2. 包含条件判断的异步逻辑

当异步流程中需要根据结果做分支判断时,async/await 的优势更明显。比如:
  1. async function checkAndUpdate() {
  2.   const currentVersion = await getCurrentVersion();
  3.   
  4.   // 同步的条件判断,自然融入异步流程
  5.   if (currentVersion < '2.0') {
  6.     const updateInfo = await fetchUpdateInfo();
  7.     if (updateInfo && updateInfo.force) {  // 增加可选链,避免updateInfo为undefined时报错
  8.       await showForceUpdateDialog();
  9.     } else {
  10.       await showOptionalUpdateToast();
  11.     }
  12.   }
  13. }
复制代码
这段代码用 Promise 写会嵌套多层then,而 async/await 让同步逻辑和异步操作无缝衔接,就像写同步代码一样自然。
3. 需要中断执行的场景

有时候我们需要在异步流程中提前返回(比如参数无效时),async/await 的写法更直观:
  1. async function submitForm(data) {
  2.   // 同步校验
  3.   if (!data?.email) {  // 增加可选链,避免data为null/undefined时报错
  4.     showError('邮箱不能为空');
  5.     return;  // 直接中断
  6.   }
  7.   
  8.   // 异步操作
  9.   try {
  10.     const validateResult = await validateRemote(data);
  11.     if (!validateResult.success) {
  12.       showError(validateResult.message);
  13.       return;  // 校验失败,中断
  14.     }
  15.     await submitData(data);
  16.     showSuccess();
  17.   } catch (err) {
  18.     handleError(err);
  19.   }
  20. }
复制代码
用 Promise 的话,需要在每个then里处理条件判断,代码会更零散。
优先用 Promise 的 3 种场景
虽然 async/await 很方便,但有些场景下,Promise 的原生 API(如Promise.all、Promise.race)更适合,甚至不可替代。
1. 并行执行多个异步操作

当多个异步任务互不依赖时,用Promise.all并行执行能大幅提高效率。比如同时加载列表数据和筛选条件:
  1. async function loadDashboard() {
  2.   // 两个请求并行执行,总耗时是较慢那个的时间
  3.   const [products, categories] = await Promise.all([
  4.     fetchProducts(),
  5.     fetchCategories()
  6.   ]);
  7.   
  8.   renderProducts(products);
  9.   renderFilters(categories);
  10. }
复制代码
如果用await逐个调用,会变成串行执行(总耗时是两者之和),完全没必要:
  1. // 不推荐:串行执行,浪费时间
  2. const products = await fetchProducts();
  3. const categories = await fetchCategories();  // 等第一个完成才开始
复制代码
2. 需要超时控制的异步操作

Promise.race可以实现 “谁先完成就用谁的结果”,非常适合超时控制。比如 “3 秒内没返回就显示加载失败”:
  1. // 封装一个带超时的异步函数
  2. function withTimeout(promise, timeoutMs = 3000) {
  3.   let timer;  // 将timer提升到外部作用域
  4.   const timeoutPromise = new Promise((_, reject) => {
  5.     timer = setTimeout(() => {
  6.       reject(new Error('请求超时'));
  7.     }, timeoutMs);
  8.   });
  9.   return Promise.race([
  10.     promise,
  11.     timeoutPromise
  12.   ]).finally(() => clearTimeout(timer));  // 确保始终清除定时器
  13. }
  14. // 使用
  15. async function loadData() {
  16.   try {
  17.     const data = await withTimeout(fetchLargeData());
  18.     render(data);
  19.   } catch (err) {
  20.     showError(err.message);  // 可能是超时错误
  21.   }
  22. }
复制代码
这段代码用 async/await 无法实现,必须依赖 Promise 的race方法。
3. 处理动态数量的异步任务

当需要处理不确定数量的异步操作(比如批量上传多个文件),Promise.all是最佳选择:
  1. async function uploadFiles(files) {
  2.   if (!files?.length) return;  // 增加空值判断,避免空数组或undefined时执行无效操作
  3.   
  4.   // 生成一个包含所有上传Promise的数组
  5.   const uploadPromises = files.map(file => {
  6.     return uploadFile(file);  // 每个文件的上传是异步操作
  7.   });
  8.   
  9.   // 等待所有文件上传完成
  10.   const results = await Promise.all(uploadPromises);
  11.   
  12.   // 处理结果
  13.   const successCount = results.filter(r => r?.success).length;  // 增加可选链容错
  14.   showMessage(`成功上传 ${successCount}/${files.length} 个文件`);
  15. }
复制代码
这种动态场景下,Promise 的数组处理能力比 async/await 更高效。
混搭使用:发挥各自优势
实际开发中,两者往往结合使用效果最好。比如先并行获取基础数据,再串行处理后续逻辑:
  1. async function buildReport() {
  2.   // 第一步:并行获取不相关的数据(提高效率)
  3.   const [users, orders, products] = await Promise.all([
  4.     fetchUsers(),
  5.     fetchOrders(),
  6.     fetchProducts()
  7.   ]);
  8.   
  9.   // 第二步:串行处理依赖关系的逻辑
  10.   const userStats = await calculateUserStats(users);
  11.   const orderSummary = await generateOrderSummary(orders, userStats);  // 依赖userStats
  12.   const report = await compileReport(orderSummary, products);  // 依赖前两者
  13.   
  14.   return report;
  15. }
复制代码
这段代码先用Promise.all并行请求,节省时间;再用 async/await 处理有依赖的串行逻辑,兼顾效率和可读性。
避坑指南:这些错误别犯

  • 不要在循环中直接用 await
循环中用await会导致串行执行,如需并行,改用Promise.all:
  1. // 错误:串行执行,慢!
  2. for (const file of files) {
  3.   await uploadFile(file);
  4. }
  5. // 正确:并行执行,快!
  6. await Promise.all(files.map(file => uploadFile(file)));
复制代码

  • 别忘了 try/catch
async/await 中任何await的 Promise reject 都会触发异常,必须用try/catch捕获,否则会导致程序崩溃。

  • 不要把 async 函数当同步函数用
async 函数永远返回 Promise,调用时必须用await或.then处理,直接调用会拿到 Promise 对象而非结果。

  • 避免过度封装
简单的异步操作(比如单个请求)没必要包成 async 函数,直接返回 Promise 更简洁。

  • 注意 Promise.all 的失败快速失败特性
Promise.all中任何一个 Promise reject 都会立即触发整个 Promise reject,如需等待所有结果(无论成功失败),可使用Promise.allSettled:
  1. // 等待所有任务完成,无论成功失败
  2. // 文件上传场景优化
  3. const results = await Promise.allSettled(uploadPromises);
  4. const failedFiles = results
  5.   .filter(r => r.status === 'rejected')
  6.   .map((r, i) => ({ file: files[i].name, error: r.reason }));
复制代码
总结:一句话记住用法


  • 用 async/await:处理线性依赖、包含条件判断、需要中断的异步流程;
  • 用 Promise API:处理并行任务(all)、超时控制(race)、动态异步数组;
  • 最佳实践:两者结合,并行任务用Promise.all,后续逻辑用 async/await 串联。
说到底,选择的核心是可读性和效率—— 哪种写法让团队成员更容易理解,哪种方式能让程序跑得更快,就用哪种。技术没有绝对的好坏,适合场景的才是最好的。

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册