前端异步编程的终极指南:从 Promise 到 async/await,这一次彻底搞懂!

🌟 前端异步编程的终极指南:从 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 变为 fulfilledrejected,就不能再改变。就像快递一旦签收或拒收,状态就定死了,不可能再变回"运输中"。

这就像一段感情 💔 --- 从"暧昧期(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()

执行流程

  1. 同时发出两个 fetch 请求 📤
  2. 等两个请求都回来 → 得到两个 Response 对象 📦
  3. 同时解析两个 Response 为 JSON 🔄
  4. 等两个 JSON 都解析完 → 拿到最终数据 ✅
  5. 任何环节出错 → 进 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,写出更优雅、更高效的代码!💪

记住:异步编程不只是技术,更是一种思维方式。学会"承诺",学会"等待",你的代码会变得更加美好!🌈


相关推荐
Cobyte1 小时前
17.响应式系统比对:链表如何实现响应式系统的高效更新
前端·javascript·vue.js
虚心的百褶裙2 小时前
JavaScript性能优化
开发语言·javascript·性能优化
凯小默2 小时前
10.JavaScript 中的类和对象以及继承
javascript·继承
AI视觉网奇2 小时前
BambuStudio 自定义加载页面
服务器·前端·javascript
漂流瓶jz3 小时前
理解Webpack插件机制:从插件使用、各类编译对象、Tapable到自定义插件与钩子开发
前端·javascript·webpack
Jackson__15 小时前
为了拯救我的腰椎和颈椎,我做了款浏览器插件!
前端·javascript·开源
林小帅15 小时前
NestJS v11 + Prisma v7 ESM 迁移配置解析
前端·javascript·后端
Hilaku17 小时前
为什么你的大文件断点续传,一遇到弱网就崩溃?
前端·javascript·程序员
林恒smileZAZ18 小时前
`Array(100).map(() => 1)` 为什么全为空?
前端·javascript