🌟 前端异步编程的终极指南:从 Promise 到 async/await,这一次彻底搞懂!
你是否曾在深夜被回调地狱折磨得痛不欲生?是否在 .then() 链中迷失了方向?别怕,这篇文章将带你从零到一,彻底征服 JavaScript 异步编程!🚀
📖 前言:为什么你需要掌握 Promise?
还记得那个"回调地狱"的时代吗?层层嵌套的回调函数,让代码像意大利面条一样难以维护。Promise 的出现,就像是黑暗中的一道光✨,它优雅地解决了异步编程的痛点,让我们的代码更加清晰、可读、可维护。
今天,我们就来一场 Promise 的深度之旅,从基础概念到实战应用,一次性把异步编程的底裤扒个干净!🧐
1️⃣ 什么是 Promise?
- Make a promise --- 许下一个诺言 💬
- Promise 是 JavaScript 处理异步操作的一种方式,就像一个 "承诺":我现在开始做一件事,做完(成功或失败)再通知你。
想象一下,你在餐厅点了一份牛排 🥩,服务员给你一张等待号码牌,这就是一个 Promise。当牛排做好了(成功),它会通知你;如果厨房出问题了(失败),它也会告诉你原因。而在这期间,你不需要傻傻地站在厨房门口等待,可以去刷会儿抖音📱!
2️⃣ Promise 的三种状态 🔄
javascript
Promise {<pending>} // ⏳ 执行中... 待处理,未完成
Promise {<fulfilled>} // ✅ 已完成,成功
Promise {<rejected>} // ❌ 已完成,失败
关键原则 :状态一旦从 pending 变为 fulfilled 或 rejected,就不能再改变。就像快递一旦签收或拒收,状态就定死了,不可能再变回"运输中"。
这就像一段感情 💔 --- 从"暧昧期(pending)"到"在一起(fulfilled)"或"分手(rejected)",一旦确定了关系,就回不去了!(当然,现实可能更复杂,但在 Promise 的世界里就是这样铁律!)
3️⃣ Promise 的基本用法 🛠️
3.1 创建 Promise
javascript
new Promise((resolve, reject) => {
// 异步操作
// 成功时调用 resolve(结果)
// 失败时调用 reject(原因)
})
3.2 fetch 返回的就是 Promise
javascript
const promise = fetch('https://xxx/api')
// fetch 一调用就立刻返回一个 Promise,此时状态是 pending
console.log(promise) // Promise {<pending>}
// 用 .then() 等它完成
promise.then(response => {
console.log(response) // 这里拿到的是 fulfilled 的结果
})
⚠️ 常见误区 :
console.log时看到pending是因为代码执行太快,网络请求还没回来。过一会儿在控制台手动输入变量名看到fulfilled,是因为请求已经完成了。这是时间差造成的,不是 bug,是 JavaScript 执行机制的正常表现!
3.3 response.json() 为什么也返回 Promise?
fetch 拿到了服务器响应,但 body 数据(JSON 字符串)可能很大,解析需要时间。所以 .json() 也是异步的,同样返回 Promise。
javascript
const response = await fetch('https://xxx/api') // 等网络请求完成(拿到快递包裹)
const data = await response.json() // 再等 JSON 解析完成(拆箱验货)
📦 生动比喻 :fetch = 快递签收,.json() = 拆箱验货。签收了不代表拆完了,拆箱也需要时间,尤其是买的是一整套乐高 🧱!
💡 进阶技巧 :如果你用
await response.json()只等一次,那就是串行解析。如果有多个 response,可以用Promise.all(responses.map(r => r.json()))并行解析,和并行请求是同一个道理。
3.4 .then() 详解 --- 链式调用的关键 🔗
.then() 是 Promise 最核心的方法,它永远返回一个新的 Promise ,所以可以一直 .then().then().then() 链下去。
javascript
fetch('https://xxx/api')
.then(response => {
console.log('第一步:拿到响应')
return response.json() // return 一个 Promise
})
.then(data => {
console.log('第二步:拿到解析后的数据', data)
return data.id // return 一个普通值
})
.then(id => {
console.log('第三步:拿到上一步 return 的值', id)
})
📋 关键规则:
.then()里return一个 Promise → 下一个.then()等它完成,拿到结果.then()里return一个普通值 → 下一个.then()直接拿到这个值.then()里什么都不return→ 下一个.then()拿到undefined
🏭 生动比喻 :就像工厂流水线,每个工人(.then())处理完后把半成品传给下一个工人,直到最终出成品。如果某个工人说"我这儿没东西了"(不 return),下一个工人就只能收到空气(undefined)!
4️⃣ async / await --- 更优雅的写法 ✨
async/await 是 Promise 的语法糖,让异步代码看起来像同步代码,可读性直接拉满!
javascript
// async 函数自动返回 Promise
const getData = async () => fetch('https://xxx/api')
async function main() {
const result = await getData() // await 等 Promise 完成,直接拿到结果
console.log(result)
}
main()
4.1 async/await 的错误处理 --- try/catch 🥅
javascript
async function main() {
try {
// fetch 两个 API(见第8章实战示例)
const res = await Promise.all([
fetch('https://v1.hitokoto.cn/?c=i&encode=json'),
fetch('https://data.ratp.fr/api/datasets/1.0/search/?q=paris'),
])
const result = await Promise.all(res.map(r => r.json()))
console.log(result)
} catch (error) {
// 任何一个 await 失败了,都会跳到这里
console.log('出错了:', error)
}
}
main()
| 写法 | 成功处理 | 失败处理 |
|---|---|---|
.then().catch() |
.then() |
.catch() |
async/await |
await 下一行 |
try { } catch { } |
5️⃣ Promise.all() --- 并行执行多个 Promise ⚡
当多个请求互不依赖时,应该让它们同时出发,而不是一个一个等:
javascript
// ❌ 不好的写法 ------ 串行(上一个完成才开始下一个)
const a = await getStory() // 等...
const b = await getRatp() // 再等...
// ✅ 好的写法 ------ 并行(同时出发,等全部完成)
const [a, b] = await Promise.all([getStory(), getRatp()])
5.1 Promise.all 的特点
- 参数是一个 Promise 数组
- 不管谁先完成、谁后完成,结果顺序按数组顺序排列 📊
- 等最后一个 Promise 完成后才 resolve
- 只要有一个失败,整体就失败,不再等待其他 Promise
5.2 Promise.all 失败处理 --- 一个失败,全部失败 💥
javascript
Promise.all([p1, p2, p3])
.then(results => {
// 全部成功才进这里
})
.catch(error => {
// 只要有一个失败,立刻进 catch
// error 是第一个失败的原因
})
🍔 打个比方:你同时点了奶茶、炸鸡、蛋糕三个外卖,Promise.all 就是等三份都到了才开吃。如果炸鸡店关门了(rejected),你就直接取消整桌(进 catch),不等蛋糕了。虽然有点残酷,但这就是 Promise.all 的"团队精神"!
6️⃣ .finally() --- 不管成功失败都执行 🏁
javascript
fetch('https://xxx/api')
.then(res => res.json())
.then(data => console.log('成功', data))
.catch(err => console.log('失败了', err))
.finally(() => {
// 不管成功还是失败,都会执行
// 适合做"收尾工作",比如隐藏 loading 动画
console.log('请求结束(无论成败)')
})
javascript
// async/await 版本
async function main() {
try {
const data = await fetch('https://xxx/api').then(r => r.json())
console.log('成功', data)
} catch (err) {
console.log('失败了', err)
} finally {
console.log('请求结束(无论成败)') // 收尾
// 比如:关闭 loading 动画、释放资源等
}
}
7️⃣ Promise 其他静态方法 🎯
除了 Promise.all,还有几个常用的:
| 方法 | 行为 | 比喻 |
|---|---|---|
Promise.all() |
全部成功才成功,一个失败全失败 | 👨👩👧👦 等所有人到齐才出发 |
Promise.race() |
第一个完成的决定结果(不管成功还是失败) | 🏁 谁先到就用谁的 |
Promise.allSettled() |
等全部完成,不管成功失败都拿到每个结果 | 📋 等所有人,记录每个人到了还是没到 |
Promise.any() |
第一个成功的为准,全部失败才失败 | 🎯 只要有一个成功就行 |
7.1 Promise.race() --- 竞速 🏎️
javascript
// 谁先回来就用谁的结果
const result = await Promise.race([
fetch('https://api1.com'), // 假设 100ms
fetch('https://api2.com'), // 假设 200ms
])
// result 是 api1 的响应,因为 api1 更快
应用场景:设置超时机制。你可以用一个定时器 Promise 和真实请求进行 race,如果请求超过 5 秒还没返回,就直接超时处理!
7.2 Promise.allSettled() --- 等全部完成,不管成败 📊
javascript
const results = await Promise.allSettled([p1, p2, p3])
// results = [
// { status: 'fulfilled', value: '成功结果' },
// { status: 'rejected', reason: '失败原因' },
// { status: 'fulfilled', value: '成功结果' },
// ]
// 不会进 catch,每个结果都拿到了,你需要自己判断 status
适用场景:你想同时发多个请求,即使有些失败了,你也要拿到成功的那些数据。比如批量上传文件,有些成功有些失败,你希望全部完成后统计结果。
7.3 Promise.any() --- 只要有一个成功 🎉
javascript
const result = await Promise.any([
fetch('https://主服务器'),
fetch('https://备用服务器1'),
fetch('https://备用服务器2'),
])
// 三台服务器只要有一台返回成功,就用那个结果
适用场景:高可用服务设计,多个备用服务器,谁先响应成功就用谁,提高系统容错能力!
8️⃣ 实战示例 🔥
8.1 async/await 方式 ✍️
javascript
const getStory = async () => fetch('https://v1.hitokoto.cn/?c=i&encode=json')
const getRatp = async () => fetch('https://data.ratp.fr/api/datasets/1.0/search/?q=paris')
async function main() {
try {
// 第一步:并行请求两个 API
const res = await Promise.all([getStory(), getRatp()])
// 第二步:把每个 response 转成 JSON(也是异步的,继续并行)
const result = await Promise.all(res.map(item => item.json()))
console.log(result) // 两个 API 的数据都拿到了
} catch (error) {
console.log('请求失败:', error)
}
}
main()
执行流程:
- 同时发出两个 fetch 请求 📤
- 等两个请求都回来 → 得到两个 Response 对象 📦
- 同时解析两个 Response 为 JSON 🔄
- 等两个 JSON 都解析完 → 拿到最终数据 ✅
- 任何环节出错 → 进 catch 🏥
8.2 .then() 链式方式 🔗
javascript
const getStory = async () => fetch('https://v1.hitokoto.cn/?c=i&encode=json')
const getRatp = async () => fetch('https://data.ratp.fr/api/datasets/1.0/search/?q=paris')
async function main() {
Promise.all([getStory(), getRatp()])
.then(responses => {
// 全部请求完成,解析 JSON
return Promise.all(responses.map(res => res.json()))
})
.then(results => {
console.log(results) // 最终数据
})
.catch(error => {
// 任何一个环节失败,都会进这里
console.log(error)
})
}
main()
💡 温馨提示 :
catch一定要放在链式调用的最后面 ,这样才能捕获到链上任何一个.then()中抛出的错误!
9️⃣ 总结对比 📝
| 写法 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
.then().catch() |
链式清晰,错误统一处理 | 嵌套多了像"回调地狱" | 简单的链式操作 |
async/await |
像同步代码,好读好写 | 错误要用 try/catch 包住 |
复杂业务逻辑,推荐 ⭐ |
串行 await 一个个等 |
简单直观 | 浪费等待时间 | 有依赖关系的请求 |
Promise.all 并行 |
速度快,互不阻塞 | 一个失败全部失败 | 互不依赖的多个请求 |
Promise.allSettled |
一个失败不影响其他 | 需要自己判断每个结果状态 | 批量操作,需要部分成功 |
Promise.race 竞速 |
取最快的响应 | 慢的请求结果被丢弃 | 超时控制 |
Promise.any 任一成功 |
多备份容错 | 全部失败才报错 | 高可用服务 |
🎬 结语
Promise 是 JavaScript 异步编程的基石,掌握它就等于掌握了前端开发的半壁江山。从回调地狱到 Promise,再到 async/await,JavaScript 的异步编程一直在进化。希望这篇文章能帮你彻底搞懂 Promise,写出更优雅、更高效的代码!💪
记住:异步编程不只是技术,更是一种思维方式。学会"承诺",学会"等待",你的代码会变得更加美好!🌈