从回调地狱到 Promise.then,再到 async/await

——异步代码是如何一步步“变好读”的

在学习 JavaScript 异步时,几乎所有人都会经历三个阶段:

  1. 一层一层的回调,看不懂
  2. .then() 链式调用,好像清楚了
  3. async / await,终于像同步代码了

很多教程会告诉你:

“async/await 解决了回调问题”

但这句话其实并不严谨

这篇文章不讲结论式口号,而是顺着历史与动机,一步一步解释:
异步代码为什么会变成今天这个样子


一、最原始的异步:回调函数

先从一段经典代码说起:

getUser(id, user => {
  getOrders(user, orders => {
    getDetail(orders, detail => {
      console.log(detail);
    });
  });
});

很多初学者看到这段代码的第一反应是:

  • 这是不是递归?
  • 是不是 getUser 里面“必须”执行 getOrders?
  • 为什么一层套一层?

1️⃣ 这段代码到底在表达什么?

用人话翻译,其实只有一句:

等用户数据拿到以后,再去拿订单;
等订单拿到以后,再去拿详情;
最后处理结果

这不是函数调用顺序,
而是异步任务之间的依赖关系


2️⃣ 为什么会“看起来像一层套一层”?

因为你在用回调描述“下一步要做什么”:

getUser(id, callback1);
// callback1 的含义是:
// “将来 user 拿到之后,请执行这里的代码”

当下一步依赖上一步的结果时,只能写成:

上一步(结果 => {
  下一步(结果2 => {
    再下一步(...)
  });
});

📌
嵌套不是因为调用顺序,而是因为依赖关系。


3️⃣ 回调本身有没有错?

没有。

真正的问题是:

  • 可读性差
  • 嵌套深
  • 错误处理分散
  • 不符合人类“从上到下”的阅读习惯

这就是所谓的 回调地狱(Callback Hell)


二、Promise:不是消灭回调,而是“管理回调”

为了解决回调地狱,Promise 被引入了。

1️⃣ 用 Promise 重写上面的逻辑

getUser(id)
  .then(user => getOrders(user))
  .then(orders => getDetail(orders))
  .then(detail => console.log(detail))
  .catch(err => console.error(err));

你会发现几件事:

  • 不再一层套一层
  • 执行顺序从上到下
  • 错误可以统一 catch

2️⃣ 那回调还在吗?

在,而且很明显:

.then(user => ...)
.then(orders => ...)

这些 仍然是回调函数

区别在于:

回调从“你自己嵌套管理”,
变成了“Promise 帮你按顺序调度”。


3️⃣ Promise 真正解决的是什么?

Promise 并没有解决“异步”本身,而是:

  • 把异步过程抽象成一个对象(Promise)
  • 用状态机(pending / fulfilled / rejected)管理流程
  • 把回调拉平成链式结构

📌
Promise = 回调 + 状态管理 + 链式调度


三、async / await:让异步“看起来像同步”

再往后,就是大家最熟悉的写法了:

async function load() {
  const user = await getUser(id);
  const orders = await getOrders(user);
  const detail = await getDetail(orders);
  console.log(detail);
}

第一次看到这段代码,很多人会觉得:

“这不是同步代码吗?”

1️⃣ async/await 到底做了什么?

一句话说明白:

async/await 只是 Promise 的语法糖

你写的:

const user = await getUser(id);

在底层等价于:

getUser(id).then(user => {
  // 从这里继续执行后面的代码
});

📌
“继续执行后面的代码”本身,就是一个回调。

只是:

  • 这个回调不是你写的
  • 是 JS 引擎自动帮你生成的

2️⃣ async/await 消灭了什么?

❌ 没有消灭回调
❌ 没有消灭 Promise
❌ 没有消灭异步

它真正消灭的是:

人脑理解异步流程的成本


四、then 和 await:是不是随便选?

不是。

它们的能力等价,但使用场景不同

1️⃣ 什么时候用 await

👉 写业务流程的时候

async function page() {
  const user = await getUser(id);
  const orders = await getOrders(user);
  render(orders);
}

原因很简单:

  • 符合人类顺序思维
  • 易读、易维护
  • try/catch 像同步异常

2️⃣ 什么时候用 .then()

👉 写底层工具 / 库 / 可组合逻辑时

function getUser(id) {
  return fetch(`/user/${id}`).then(r => r.json());
}

让调用者自己决定:

  • .then()
  • 还是 await

3️⃣ 并发场景:then 和 Promise 才是核心

const [a, b] = await Promise.all([
  fetchA(),
  fetchB()
]);

📌
你会发现:

await 从来没有取代 Promise
它只是“等待 Promise 结果”的方式


五、一个重要但常被忽略的结论

异步世界里,不可能没有回调

因为:

  • IO 何时完成不由你决定
  • 网络何时返回不可预测
  • 事件何时发生不可预测

你能做的只有一件事:

提前告诉系统:事情完成以后,你要做什么

这,就是回调。

Promise 和 async/await,只是让这件事更优雅


六、整条演进路线的一句话总结

  • 回调:你自己管理“下一步”
  • Promise.then:框架帮你管理回调顺序
  • async/await:语法帮你把回调“铺平成同步代码”

📌
本质一直没变,变的是“人和回调之间的距离”。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐