跳到主要内容

Promise:取餐号的艺术

从回调到 Promise

上一节我们见识了回调地狱的恐怖。现在该 Promise 登场了。

还是那个餐厅的比方:

  • 回调:你告诉服务员"菜好了喊我"——但你自己没有任何凭证,全靠服务员记得。
  • Promise:服务员给你一个取餐号(Promise 对象)。这个号码牌有三种状态:
    • 等待中(Pending):厨房还在做
    • 已完成(Fulfilled):菜做好了,可以取餐
    • 已拒绝(Rejected):做不了(比如食材用完了)

拿着取餐号,你可以说:"等菜好了.then),我要加点醋;如果做不了.catch),给我换一道菜。"

Promise 基础

加载代码编辑器中……

来拆解一下 JS 的 Promise:

  1. new Promise((resolve, reject) => { ... }) — 创建一个 Promise,里面的函数立即执行
  2. resolve(value) — 标记为"成功",把结果传出去
  3. reject(reason) — 标记为"失败",把错误原因传出去
  4. .then(callback) — "成功了之后做什么"
  5. .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 有一个类似的概念叫 Futureconcurrent.futures.Futureasyncio.Future),它也代表"一个还没完成的操作"。不过在 Python 中,你几乎不会直接操作 Future——Python 选择了直接跳到 async/await,把底层的 Future 藏起来了。

🏋️AI 练习

用 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"))
})
})

logingetProfile 改成返回 Promise 的版本,然后用 .then() 链式调用。

提示:可以让 AI 帮你检查答案是否正确。

小结

概念PythonJavaScript
Promise无原生支持new Promise((resolve, reject) => ...)
链式调用不需要(同步就行).then().then().catch()
并行等待asyncio.gather(后面学)Promise.all([...])
竞速asyncio.wait(FIRST_COMPLETED)Promise.race([...])
类似概念FuturePromise

一句话:Promise 是 JS 对回调地狱的反击。 用取餐号代替口头通知,用链式调用代替层层嵌套——代码终于可以一行一行往下读了。但 Promise 链写多了也有点烦……有没有更好的方式?下一节的 async/await 就是答案。