Promise:取餐号的艺术
从回调到 Promise
上一节我们见识了回调地狱的恐怖。现在该 Promise 登场了。
还是那个餐厅的比方:
- 回调:你告诉服务员"菜好了喊我"——但你自己没有任何凭证,全靠服务员记得。
- Promise:服务员给你一个取餐号(Promise 对象)。这个号码牌有三种状态:
- 等待中(Pending):厨房还在做
- 已完成(Fulfilled):菜做好了,可以取餐
- 已拒绝(Rejected):做不了(比如食材用完了)
拿着取餐号,你可以说:"等菜好了(.then),我要加点醋;如果做不了(.catch),给我换一道菜。"
Promise 基础
来拆解一下 JS 的 Promise:
new Promise((resolve, reject) => { ... })— 创建一个 Promise,里面的函数立即执行resolve(value)— 标记为"成功",把结果传出去reject(reason)— 标记为"失败",把错误原因传出去.then(callback)— "成功了之后做什么".catch(callback)— "失败了之后做什么"
Promise 的三种状态
resolve(value)
Pending ─────────────────→ Fulfilled(已完成)
│ │
│ reject(reason) │ .then(value => ...)
└────────────────────→ Rejected(已拒绝)
│
.catch(err => ...)
关键规则:状态一旦改变就不可逆。一个 Promise 只能从 Pending 变成 Fulfilled 或 Rejected,不能回头,也不能再变。就像取餐号,要么出餐成功,要么告诉你做不了——不会反复横跳。
链式调用:告别回调地狱
Promise 最厉害的地方在于:.then() 返回的还是一个 Promise!这意味着你可以把多个异步操作串成一条链,而不是嵌套成金字塔:
对比上一节的回调地狱:
- 回调版:越嵌越深
))})})})← 这种括号地狱 - Promise 版:
.then().then().then()← 一条平坦的链
而且只需要在链尾写一个 .catch() 就能捕获整条链上任何一步的错误。
.finally():不管成败都要做的事
有时候你需要在操作结束后执行一些"善后工作"——不管成功还是失败都要做。比如隐藏加载动画:
Promise.all:并行等待
有时候你要同时发多个请求,全部完成后再处理。就像点了三道菜,要等三道菜都上齐了才开动:
Promise.all 接受一个 Promise 数组,全部成功后返回所有结果。如果有任何一个失败,整个 Promise.all 立刻进入 rejected 状态。
Promise.race:谁先好就要谁
相比 Promise.all(等所有菜上齐),Promise.race 是谁先好就吃谁:
最常见的 Promise 错误是忘记写 .catch()!
如果一个 Promise 被 reject 了但没有 .catch() 来处理,在浏览器中会看到一个红色的 Unhandled Promise Rejection 警告。在 Node.js 中,它甚至可能导致进程崩溃。
所以记住:有 .then() 的地方,就应该有 .catch()——就像去餐厅要有备选方案,万一想吃的菜卖完了,你得知道换什么。
Promise 速查表
| 方法 | 说明 | 比喻 |
|---|---|---|
new Promise((resolve, reject) => ...) | 创建 Promise | 给厨房下单 |
.then(value => ...) | 成功后做什么 | 菜好了之后…… |
.catch(err => ...) | 失败后做什么 | 做不了的话…… |
.finally(() => ...) | 无论成败 | 结账走人 |
Promise.all([p1, p2, p3]) | 全部完成 | 等所有菜上齐 |
Promise.race([p1, p2, p3]) | 第一个完成 | 谁先做好吃谁 |
Python 有 Promise 吗?
严格来说没有。但 Python 有一个类似的概念叫 Future(concurrent.futures.Future 或 asyncio.Future),它也代表"一个还没完成的操作"。不过在 Python 中,你几乎不会直接操作 Future——Python 选择了直接跳到 async/await,把底层的 Future 藏起来了。
用 Promise 改写下面的回调代码:
function login(user, callback) {
setTimeout(() => callback("token_abc123"), 500)
}
function getProfile(token, callback) {
setTimeout(() => callback({ name: "小明", vip: true }), 500)
}
// 回调版
login("xiaoming", (token) => {
getProfile(token, (profile) => {
console.log(profile.name + (profile.vip ? " 是VIP" : " 不是VIP"))
})
})
把 login 和 getProfile 改成返回 Promise 的版本,然后用 .then() 链式调用。
提示:可以让 AI 帮你检查答案是否正确。
小结
| 概念 | Python | JavaScript |
|---|---|---|
| Promise | 无原生支持 | new Promise((resolve, reject) => ...) |
| 链式调用 | 不需要(同步就行) | .then().then().catch() |
| 并行等待 | asyncio.gather(后面学) | Promise.all([...]) |
| 竞速 | asyncio.wait(FIRST_COMPLETED) | Promise.race([...]) |
| 类似概念 | Future | Promise |
一句话:Promise 是 JS 对回调地狱的反击。 用取餐号代替口头通知,用链式调用代替层层嵌套——代码终于可以一行一行往下读了。但 Promise 链写多了也有点烦……有没有更好的方式?下一节的 async/await 就是答案。