🤔 为什么需要 Promise?
在 ES6 之前,处理异步操作(如网络请求、定时器)主要靠回调函数(Callback)。当逻辑复杂时,容易陷入“回调地狱”(Callback Hell),代码嵌套层级深,难以维护。
通俗比喻:
想象你去餐厅点餐(发起异步请求)。
- 回调地狱模式:你站在柜台前,服务员说“等菜好了叫你”,你不敢走;菜好了,服务员说“等汤好了叫你”,你还得站着……直到所有流程结束。
- Promise 模式:服务员给你一张取餐号小票(Promise 对象)。你可以去旁边坐着玩手机(执行其他代码)。
- 如果菜做好了(成功),服务员叫号,你去取餐(
.then)。- 如果菜卖完了(失败),服务员道歉,你决定换一家或吃泡面(
.catch)。- 无论成功还是失败,最后都要把桌子收拾干净(
.finally)。这张“小票”及其附带的服务规则,就是Promise 原型上的方法。
📂 目录
- 🛠️ 核心概念:Promise 的状态机
- 🔧 实例方法:处理结果与异常
- 🏭 静态方法:并发控制与工具函数
- 💻 代码实战:常见场景演练
- ⚠️ 常见误区与最佳实践
- 💡 总结
1. 🛠️ 核心概念:Promise 的状态机
Promise 对象代表一个异步操作的最终完成(或失败)及其结果值。它有三种状态,且状态一旦改变,不可逆:
| 状态 | 英文 | 说明 |
|---|---|---|
| Pending | 进行中 | 初始状态,既不是成功,也不是失败。 |
| Fulfilled | 已成功 | 操作成功完成,有一个值。 |
| Rejected | 已失败 | 操作失败,有一个原因(错误信息)。 |
注意:我们通常调用的
.then(),.catch()等方法,都是挂载在Promise.prototype上的实例方法。而Promise.all(),Promise.race()等是挂载在Promise构造函数上的静态方法。
2. 🔧 实例方法:处理结果与异常
这些方法用于注册回调,当 Promise 状态改变时被调用。
✅ 1..then(onFulfilled, onRejected)
- 作用:指定Resolved和Rejected状态的回调函数。
- 返回值:返回一个新的 Promise 对象。这使得我们可以进行链式调用。
- 参数:
onFulfilled: 可选,成功时的回调。onRejected: 可选,失败时的回调(通常建议用.catch代替)。
constpromise=newPromise((resolve,reject)=>{setTimeout(()=>resolve("数据加载成功"),1000);});promise.then((value)=>{console.log(value);// "数据加载成功"returnvalue+" - 处理完毕";// 返回值会传递给下一个 then}).then((newValue)=>{console.log(newValue);// "数据加载成功 - 处理完毕"});✅ 2..catch(onRejected)
- 作用:专门处理 Rejected 状态或链式中抛出的错误。
- 本质:它是
.then(null, onRejected)的语法糖。 - 优势:可以捕获前面所有
.then中发生的同步错误。
promise.then((value)=>{thrownewError("处理过程中出错了");}).catch((error)=>{console.error(error.message);// "处理过程中出错了"});✅ 3..finally(onFinally)
- 作用:无论 Promise 最终是 Fulfilled 还是 Rejected,都会执行的回调。
- 场景:关闭 Loading 动画、隐藏弹窗、清理资源等。
- 注意:
onFinally不接受参数,也不影响最终的返回值(除非抛出错误)。
letisLoading=true;fetch("/api/data").then((res)=>res.json()).catch((err)=>console.error(err)).finally(()=>{isLoading=false;// 无论成功失败,都停止加载状态console.log("请求结束");});3. 🏭 静态方法:并发控制与工具函数
这些方法直接挂在Promise构造函数上,用于创建或组合多个 Promise。
✅ 1.Promise.resolve(value)
- 作用:快速创建一个状态为 Fulfilled 的 Promise。
- 场景:将普通值或非 Promise 对象包装成 Promise,以便统一使用
.then处理。
Promise.resolve("Hello").then((val)=>console.log(val));// "Hello"✅ 2.Promise.reject(reason)
- 作用:快速创建一个状态为 Rejected 的 Promise。
- 场景:在函数开头进行参数校验,失败时直接返回拒绝态。
functioncheckAge(age){if(age<18){returnPromise.reject("未成年人禁止访问");}returnPromise.resolve("访问允许");}✅ 3.Promise.all(iterable)【高频面试点】
- 作用:并行执行多个 Promise,所有都成功才成功,任何一个失败则立即失败。
- 返回值:一个包含所有结果的数组(顺序与输入一致)。
- 场景:同时请求用户信息和订单列表,两者都拿到后才渲染页面。
constp1=Promise.resolve(1);constp2=Promise.resolve(2);constp3=Promise.reject("Error");// 全部成功Promise.all([p1,p2]).then((values)=>console.log(values));// [1, 2]// 有一个失败Promise.all([p1,p3]).catch((err)=>console.error(err));// "Error"✅ 4.Promise.race(iterable)
- 作用:并行执行多个 Promise,谁先改变状态(无论成功还是失败),就采用谁的结果。
- 场景:超时控制。比如请求接口,如果 5秒 没返回,就判定超时。
constrequest=fetch("/api/slow-data");consttimeout=newPromise((_,reject)=>setTimeout(()=>reject("请求超时"),5000),);Promise.race([request,timeout]).then((res)=>console.log("成功",res)).catch((err)=>console.error("失败",err));✅ 5.Promise.allSettled(iterable)【ES2020】
- 作用:并行执行多个 Promise,等待所有任务结束(不管成功还是失败)。
- 返回值:一个对象数组,每个对象包含
status(‘fulfilled’ 或 ‘rejected’) 和value/reason。 - 场景:批量上传文件,想知道哪些成功了,哪些失败了,而不是因为一个失败就全盘否定。
constp1=Promise.resolve(1);constp2=Promise.reject("Fail");Promise.allSettled([p1,p2]).then((results)=>{results.forEach((result)=>{if(result.status==="fulfilled"){console.log("成功:",result.value);}else{console.log("失败:",result.reason);}});});// 输出:// 成功: 1// 失败: Fail✅ 6.Promise.any(iterable)【ES2021】
- 作用:并行执行多个 Promise,只要有一个成功,就返回那个成功的结果。只有全部失败,才返回失败(聚合错误)。
- 场景:从多个镜像源下载资源,哪个快用哪个。
4. 💻 代码实战:常见场景演练
场景 1:串行依赖请求(Chain)
第二个请求依赖第一个请求的结果。
getUserInfo(userId).then((user)=>{returngetOrderList(user.id);// 返回新的 Promise}).then((orders)=>{console.log("用户订单:",orders);}).catch((err)=>{console.error("流程出错:",err);});场景 2:并行独立请求(All)
两个请求互不依赖,同时发起以节省时间。
Promise.all([getBannerData(),getRecommendList()]).then(([banners,recommends])=>{renderPage(banners,recommends);}).catch((err)=>{showToast("页面加载失败");});场景 3:带超时的请求封装
functionfetchWithTimeout(url,timeout=5000){constcontroller=newAbortController();constid=setTimeout(()=>controller.abort(),timeout);returnfetch(url,{signal:controller.signal}).then((res)=>{clearTimeout(id);returnres.json();}).catch((err)=>{if(err.name==="AbortError"){thrownewError("请求超时");}throwerr;});}5. ⚠️ 常见误区与最佳实践
❌ 误区 1:在.then中忘记return
如果在.then中返回了一个普通的值,下一个.then能收到;但如果返回了一个 Promise,下一个.then会等待这个 Promise 结算。如果不 return,后续链条可能拿到undefined。
❌ 误区 2:混淆Promise.all和Promise.allSettled
- 如果你希望“要么全成,要么全败”,用
all。 - 如果你希望“不管成败,我都要知道每个任务的结果”,用
allSettled。
✅ 最佳实践:始终使用.catch或try...catch
未处理的 Promise rejection 会导致控制台警告,甚至在 Node.js 进程中导致退出。
// Async/Await 风格下的错误处理asyncfunctionloadData(){try{constdata=awaitfetch("/api/data");// ...}catch(error){console.error("捕获异常:",error);}}✅ 最佳实践:避免嵌套.then
尽量保持扁平化的链式调用,或者直接使用async/await,后者可读性更好。
6. 💡 总结
| 方法类型 | 方法名 | 核心作用 | 关键特点 |
|---|---|---|---|
| 实例方法 | .then() | 处理成功/失败 | 链式调用,返回新 Promise |
.catch() | 处理异常 | 捕获前面所有的错误 | |
.finally() | 最终清理 | 必执行,无参数 | |
| 静态方法 | Promise.resolve() | 创建成功态 | 包装值 |
Promise.reject() | 创建失败态 | 包装错误 | |
Promise.all() | 并行全成功 | 短路与(一错即错) | |
Promise.race() | 竞速 | 谁快听谁的 | |
Promise.allSettled() | 并行全结算 | 记录每个任务状态 | |
Promise.any() | 竞速成功 | 只要一个成功即可 |
🚀 博主寄语:
Promise 是现代 JavaScript 异步编程的基石。
虽然async/await让代码看起来像同步的,但理解底层的 Promise 原型方法(特别是all和race的区别)对于处理复杂并发场景至关重要。记住口诀:
Then 链式传值忙,
Catch 兜底防异常。
All 要全都成功样,
Race 抢跑第一强。
Settled 不管成与败,
Any 只要一个亮。
Finally 最后收个场,
异步编程心不慌。
希望这篇文档能帮你彻底掌握 Promise 的原型方法!如果有疑问,欢迎在评论区留言。👇
喜欢这篇文章吗?记得点赞、收藏、转发哦!❤️