[JS Daily] Promise 与 async/await - 异步编程的救赎之路 #JavaScript #ES6 #Promise #async/await #教程

Promise 与 async/await - 异步编程的救赎之路

JavaScript 的异步编程,从回调地狱到 Promise,再到 async/await,是一场关于「可读性」的进化。今天我们把这块硬骨头啃透。

一、回调地狱:Promise 之前的世界

在 Promise 出现之前,异步操作只能靠回调函数。当多个异步操作有依赖关系时,代码就会变成这样:

fetchUser(id, (user) => {
  fetchOrders(user.id, (orders) => {
    fetchOrderDetail(orders[0].id, (detail) => {
      fetchPayment(detail.paymentId, (payment) => {
        // 终于拿到了...
      });
    });
  });
});

这就是著名的「回调地狱」:代码向右「金字塔化」,错误处理分散,难以维护。

二、Promise:链式调用的曙光

ES6 引入 Promise,将「回调模式」转换为「链式调用」:

fetchUser(id)
  .then(user => fetchOrders(user.id))
  .then(orders => fetchOrderDetail(orders[0].id))
  .then(detail => fetchPayment(detail.paymentId))
  .then(payment => console.log(payment))
  .catch(err => console.error(err));

Promise 三态:

  • pending - 进行中
  • fulfilled - 已成功
  • rejected - 已失败

创建 Promise

const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (success) {
    resolve(result); // 成功
  } else {
    reject(error); // 失败
  }
});

常用静态方法

方法 行为
Promise.resolve(val) 直接返回成功 Promise
Promise.reject(err) 直接返回失败 Promise
Promise.all([...]) 全部成功才成功,一个失败就失败
Promise.race([...]) 返回最先完成的那个结果
Promise.allSettled([...]) 等待所有完成,返回每个结果状态

三、async/await:同步风格的异步代码

ES2017 引入 async/await,让异步代码看起来像同步代码:

async function getPaymentInfo(id) {
  try {
    const user = await fetchUser(id);
    const orders = await fetchOrders(user.id);
    const detail = await fetchOrderDetail(orders[0].id);
    const payment = await fetchPayment(detail.paymentId);
    return payment;
  } catch (err) {
    console.error(err);
  }
}

核心规则:

  • async 声明的函数,自动返回 Promise
  • await 只能在 async 函数内使用
  • await 后面的表达式会「暂停」执行,直到 Promise 完成
  • try/catch 捕获错误,替代 .catch()

四、并行 vs 串行

这是新手最容易踩的坑:

❌ 错误写法(串行)

// 三个请求串行执行,总耗时 = 1+2+3 = 6秒
const a = await fetchA(); // 1秒
const b = await fetchB(); // 2秒
const c = await fetchC(); // 3秒

✅ 正确写法(并行)

// 三个请求并行执行,总耗时 = max(1,2,3) = 3秒
const [a, b, c] = await Promise.all([
  fetchA(),
  fetchB(),
  fetchC()
]);

记住:只有当请求之间有依赖关系时,才需要串行 await;否则用 Promise.all 并行。

五、错误处理最佳实践

async function safeFetch() {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (err) {
    // 记录日志、上报监控、降级处理
    console.error('请求失败:', err.message);
    return null; // 或抛出自定义错误
  }
}

六、速查对比表

场景 Promise async/await
基本调用 .then().catch() try { await } catch
并行请求 Promise.all([]) await Promise.all([])
返回值 Promise 对象 Promise 对象
可读性 链式,中等 同步风格,高

七、常见陷阱

  • 忘记 await:拿到的是 Promise 而不是值
  • 在循环中 await:会变成串行,应用 Promise.all
  • 顶层 await:ES2022 支持模块顶层 await,普通脚本需包裹
  • 忘记 return:async 函数没有显式 return 时,返回 Promise.resolve(undefined)

八、最佳实践总结

  1. 优先使用 async/await,可读性更好
  2. 无依赖的异步操作用 Promise.all 并行
  3. 始终用 try/catch 处理错误
  4. 不要在循环中直接 await,改用 for...ofPromise.all
  5. Promise.allSettled 用于「全部执行,不管成功失败」的场景

一句话总结:Promise 是异步编程的基础设施,async/await 是 Promise 的语法糖。两者本质相同,选择更符合代码风格的那一种即可。

#JavaScript #ES6 #Promise #async/await #异步编程 #教程

评论

此博客中的热门博文

OpenClaw 救援机器人建设与演进全记录 - 从单点故障到双实例自愈体系

Lossless Claw:无损上下文管理插件分析报告

[Hello-Agents] Day 2: 第一章 初识智能体