从一个真实的故事开始
假设你要做一个用户数据展示页面,需要:
- 先获取用户信息
- 根据用户 ID 获取订单列表
- 根据第一个订单 ID 获取订单详情
- 发送通知说"加载完成"
听起来很简单对吧?在没有 Promise 的年代,代码是这样的:
js
getUser(userId, (err, user) => {
if (err) {
console.error(err);
return;
}
getOrders(user.id, (err, orders) => {
if (err) {
console.error(err);
return;
}
getOrderDetail(orders[0].id, (err, detail) => {
if (err) {
console.error(err);
return;
}
sendNotification(detail, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log('完成');
});
});
});
});
这就是臭名昭著的回调地狱(Callback Hell)------ 代码像金字塔一样向右无限生长。如果你做过实际项目,一定写过比这嵌套更深的代码。问题是:
- 🔴 可读性极差:眼睛跟着缩进跑
- 🔴 每个回调都要重复错误处理:太啰嗦
- 🔴 try/catch 完全失效:异步错误抓不到
- 🔴 想做并发? 比如同时请求三个独立接口 ------ 用回调写起来非常痛苦
还有更隐蔽的问题:信任问题 。你把回调函数交给了第三方的异步方法 ------ 它什么时候调用你的回调?调用几次?会不会因为 bug 调了两次?会不会根本不调用?你完全控制不了。
Promise 登场:一张小票的比喻
你去奶茶店点单,店员给你一张小票 。这不是奶茶,但它代表了 你未来会拿到的那杯奶茶。你可以拿着小票继续逛街(同步代码继续执行),等奶茶做好后凭小票去取(
.then()拿到结果)。
Promise 就是这个"小票"------ 一个代表未来值的对象。它让你:
- 不用 把回调交给第三方(你持有 Promise,决定何时
.then()) - 可以链式串联多个异步操作
- 可以统一处理错误
- 可以轻松组合多个异步操作
Promise 的三种状态
Promise 是一个不可逆的状态机:
yaml
┌──→ fulfilled(已成功)
│ PromiseValue: 成功值
pending(进行中)──→─┤
│
└──→ rejected(已失败)
PromiseValue: 失败原因
核心规则:状态一旦确定,永不改变。 就像一杯奶茶,做完了就是做完了,不能再变成"正在做"。
js
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 这里的代码是**同步执行**的!
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('🎉 成功了');
} else {
reject('💥 失败了');
}
}, 1000);
});
// 消费 Promise
promise
.then(result => console.log('then:', result)) // 成功走这里
.catch(error => console.error('catch:', error)) // 失败走这里
.finally(() => console.log('无论成败都执行')); // 最终清理
理解 .then():链式调用的魔法
很多人刚开始用 Promise 时会犯一个错:把 .then() 当成"添加回调"。
不对。 .then() 永远返回一个新 Promise。这才是链式调用的基础:
js
fetchUser(1) // Promise<User>
.then(user => fetchOrders(user.id)) // Promise<Order[]>
.then(orders => getDetail(orders[0])) // Promise<Detail>
.then(detail => console.log(detail)) // Promise<undefined>
.catch(err => console.error('任何一环出错都会到这', err));
每一环 .then() 返回的 Promise,其状态由回调函数的返回值决定:
- 返回普通值 → 自动包装为
fulfilled的 Promise - 返回 Promise → 沿用那个 Promise 的状态
- 抛出异常 → 返回
rejected的 Promise
正是因为 .then() 总是返回新 Promise,链上的错误才能"冒泡"到最后的 .catch()。
async/await:让异步代码看起来像同步的
async/await 本质上是 Promise 的语法糖 ------ 没有引入任何新能力,只是写法上的优化:
js
// Promise 链式写法
function getUserData(id) {
return fetchUser(id)
.then(user => fetchOrders(user.id))
.then(orders => ({ user, orders }));
}
// async/await 写法 ------ 一样的事,更直观
async function getUserData(id) {
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
return { user, orders };
}
几个关键规则:
js
// 1. async 函数总是返回 Promise
async function fn() { return 42; }
fn(); // Promise {<fulfilled>: 42}
// 2. await 只能写在 async 函数里
// 3. await 后面的代码 = .then() 回调
// 4. 用熟悉的 try/catch 处理错误
async function getData() {
try {
const data = await fetch('/api');
return data.json();
} catch (err) {
console.error('请求失败', err);
return null; // 降级处理
}
}
本篇总结
| 概念 | 一句话 |
|---|---|
| Promise | 代表未来的异步结果,像奶茶店的小票 |
| 三种状态 | pending → fulfilled / rejected,不可逆 |
| .then() | 永远返回新 Promise,链式调用基础 |
| async/await | Promise 的语法糖,写法更像同步代码 |
下一篇我们深入 Promise 的静态方法(all / allSettled / race / any),看看如何优雅地协调多个异步操作。