news 2026/4/12 0:14:44

JavaScript学习笔记:13.Promise

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript学习笔记:13.Promise

JavaScript学习笔记:13.Promise

上一篇咱们用“设计图纸”搞定了类的封装与继承,这一篇要攻克JS开发的“异步老大难”——Promise。做前端绕不开异步:请求接口要等服务器响应、加载图片要等资源下载、定时器要等时间触发…… 而在Promise出现前,咱们只能用“回调函数”处理这些操作,写出来的代码常常像“俄罗斯套娃”——一层嵌一层,这就是让人头秃的“回调地狱”。

Promise的出现,就像给异步任务发了一封“正式承诺函”:它明确告诉程序“这个任务要么成功给你结果,要么失败给你原因,在此之前你该干啥干啥”。今天咱们就从“为什么需要Promise”讲起,用“餐厅点餐”的生活化比喻,把Promise的状态机制、链式调用、静态方法和实战技巧彻底讲透,让你再也不用面对嵌套的回调皱眉头。

一、先破案:为什么需要Promise?回调地狱有多坑?

先看一个场景:先点奶茶(异步),奶茶做好后点蛋糕(异步),蛋糕做好后打包带走(异步)。用传统回调函数写是这样的:

// 回调地狱:一层套一层,代码向右偏移,维护性极差orderMilkTea("珍珠奶茶",function(milkTea){console.log(`拿到${milkTea}`);orderCake("芝士蛋糕",function(cake){console.log(`拿到${cake}`);packFood([milkTea,cake],function(packed){console.log(`打包完成:${packed}`);// 再嵌套更多异步操作,代码就彻底乱了},function(err){console.log("打包失败",err);});},function(err){console.log("点蛋糕失败",err);});},function(err){console.log("点奶茶失败",err);});

这种代码的问题显而易见:

  1. 嵌套层级深:像爬楼梯一样,越往后越靠右,可读性差;
  2. 错误处理分散:每个回调都要单独写错误处理,重复且冗余;
  3. 无法并行/中断:多个异步任务只能串行,想并行处理或中途中断非常麻烦。

而用Promise改写后,代码瞬间“站直了”:

// Promise链式调用:平级代码,清晰易懂orderMilkTea("珍珠奶茶").then(milkTea=>{console.log(`拿到${milkTea}`);returnorderCake("芝士蛋糕");// 返回新的Promise,衔接下一个任务}).then(cake=>{console.log(`拿到${cake}`);returnpackFood([milkTea,cake]);}).then(packed=>console.log(`打包完成:${packed}`)).catch(err=>console.log("流程失败",err));// 统一错误处理

💡 核心差异:Promise把“嵌套的回调”变成了“链式的then”,错误处理也统一交给最后一个catch,代码结构瞬间清晰。这就是Promise的核心价值——规范化异步流程,解决回调地狱

二、Promise核心概念:一张“承诺函”的三个状态

Promise翻译过来是“承诺”,它的核心就是用“状态”来描述异步任务的执行结果。可以把Promise想象成“餐厅点餐单”:

  • pending(等待中):订单已提交,厨师正在做,任务还没完成;
  • fulfilled(已成功):餐做好了,任务成功完成,拿到结果;
  • rejected(已失败):食材用完了,任务失败,拿到错误原因。

这三个状态有两个铁律,是理解Promise的关键:

  1. 状态不可逆:一旦从pending变成fulfilled或rejected,就再也变不回去了——就像餐做好了不能再变回制作中,没食材了也不能再上菜;
  2. 结果唯一:成功时只有一个“结果值(value)”,失败时只有一个“原因(reason)”,不会出现既成功又失败的情况。

三、Promise基础用法:从“发承诺”到“收结果”

Promise的用法就像“点餐→等餐→用餐”,核心分三步:创建Promise、改变状态、处理结果。

1. 第一步:创建Promise(提交订单)

new Promise()创建Promise实例,接收一个“执行器函数”作为参数,这个函数有两个内置参数:resolve(成功时调用,相当于“上菜”)和reject(失败时调用,相当于“告知没餐”)。

// 封装一个“点奶茶”的Promise函数functionorderMilkTea(type){// 执行器函数:立即执行,里面包含异步任务returnnewPromise((resolve,reject)=>{setTimeout(()=>{// 模拟异步操作:1秒后完成consthasIngredient=Math.random()>0.3;// 70%概率有食材if(hasIngredient){// 成功:调用resolve,传递结果resolve(`${type}(少糖少冰)`);}else{// 失败:调用reject,传递错误原因reject(newError(`${type}失败:珍珠卖完了`));}},1000);});}

📌 注意:执行器函数会立即执行,不是等到调用then才执行——就像提交订单后,厨师立刻开始做,不是等你催单才动手。

2. 第二步:处理结果(用餐/处理没餐)

then()处理成功结果,用catch()处理失败结果,还能加finally()处理“无论成功失败都要做的事”(比如收起点餐单)。

// 调用Promise函数,处理结果orderMilkTea("珍珠奶茶").then(result=>{// 成功回调:接收resolve的结果console.log(`用餐:${result}`);}).catch(error=>{// 失败回调:接收reject的原因console.log(`处理失败:${error.message}`);}).finally(()=>{// 无论成功失败,都会执行console.log("流程结束:收起点餐单");});

执行结果(二选一):

// 成功情况 用餐:珍珠奶茶(少糖少冰) 流程结束:收起点餐单 // 失败情况 处理失败:点珍珠奶茶失败:珍珠卖完了 流程结束:收起点餐单

四、核心技巧:链式调用——异步任务的“流水线”

Promise的灵魂是“链式调用”,then()会返回一个新的Promise,让多个异步任务像流水线一样依次执行,而不是嵌套。

1. 链式调用的核心逻辑

  • 每个then()的返回值,会成为下一个then()的参数;
  • 如果then()返回的是Promise,下一个then()会等待这个Promise完成,再接收结果;
  • 任何一个环节抛出错误,都会跳过后续then(),直接进入最近的catch()
// 流水线示例:点奶茶→点蛋糕→打包orderMilkTea("珍珠奶茶").then(milkTea=>{console.log(`拿到奶茶:${milkTea}`);returnorderCake("芝士蛋糕");// 返回新Promise,衔接下一个任务}).then(cake=>{console.log(`拿到蛋糕:${cake}`);returnpackFood([milkTea,cake]);// 继续返回Promise}).then(packed=>console.log(`打包完成:${packed}`)).catch(err=>console.log(`流程失败:${err.message}`));

2. 常见坑:忘记return导致“漂浮Promise”

如果then()里启动了异步任务,但没返回它,这个任务就会“漂浮”——无法追踪状态,下一个then()会提前执行。

// 反面例子:忘记return,fetch是漂浮PromiseorderMilkTea("珍珠奶茶").then(milkTea=>{fetch("/api/recordOrder");// 没返回,无法追踪结果}).then(()=>{console.log("订单记录完成");// 可能在fetch完成前执行});// 正面例子:返回Promise,确保顺序orderMilkTea("珍珠奶茶").then(milkTea=>{returnfetch("/api/recordOrder");// 返回Promise,下一个then等待它完成}).then(()=>{console.log("订单记录完成");// 正确:fetch完成后执行});

五、错误处理:Promise的“安全网”

Promise的错误处理就像“餐厅的投诉渠道”,能统一捕获整个链条的错误,还支持细粒度处理。

1. 统一错误处理(全局投诉)

用链条末尾的catch(),捕获所有环节的错误(包括then()里抛出的异常):

orderMilkTea("珍珠奶茶").then(milkTea=>{if(milkTea.includes("珍珠")){thrownewError("我不爱喝珍珠,换椰果");// 手动抛出错误}returnorderCake("芝士蛋糕");}).then(cake=>packFood([milkTea,cake])).catch(err=>console.log(`统一处理错误:${err.message}`));// 输出:统一处理错误:我不爱喝珍珠,换椰果

2. 细粒度错误处理(局部投诉)

嵌套then()里的catch(),只处理当前环节的错误,不影响后续流程:

orderMilkTea("珍珠奶茶").then(milkTea=>{// 可选环节:加配料(失败不影响主流程)returnaddTopping(milkTea,"椰果").catch(err=>{console.log(`加配料失败:${err.message}`);returnmilkTea;// 返回原奶茶,流程继续});}).then(milkTea=>orderCake("芝士蛋糕")).then(cake=>packFood([milkTea,cake])).catch(err=>console.log(`主流程失败:${err.message}`));

3. catch后的链式恢复

catch()后还能继续接then(),实现“错误恢复”——就像投诉后餐厅补救,流程继续:

orderMilkTea("珍珠奶茶").then(milkTea=>{thrownewError("奶茶太甜了");}).catch(err=>{console.log(`处理错误:${err.message},换无糖的`);returnorderMilkTea("珍珠奶茶(无糖)");// 补救,返回新Promise}).then(milkTea=>console.log(`最终拿到:${milkTea}`)).finally(()=>console.log("流程结束"));

六、静态方法:Promise的“团队协作模式”

Promise提供了四个静态方法,处理多个异步任务的协作,就像“餐厅处理多个订单”的不同策略。

方法作用核心特点场景示例
Promise.all([p1,p2,p3])所有任务成功才成功,一个失败就失败快速失败,结果按顺序返回同时点奶茶和蛋糕,都做好才打包
Promise.allSettled([p1,p2,p3])等待所有任务完成,无论成功失败全部 settle,返回每个任务的状态和结果统计多个接口的请求结果(成功/失败都要知道)
Promise.any([p1,p2,p3])任意一个任务成功就成功,全部失败才失败快速成功,返回第一个成功的结果多个CDN加载图片,哪个快用哪个
Promise.race([p1,p2,p3])任意一个任务 settle(成功/失败)就结束先到先得,返回第一个 settle 的结果接口请求超时控制(请求和定时器赛跑)

实战示例:

// 1. Promise.all:全部成功才返回constp1=orderMilkTea("珍珠奶茶");constp2=orderCake("芝士蛋糕");Promise.all([p1,p2]).then([milkTea,cake]=>packFood([milkTea,cake])).then(packed=>console.log(`打包完成:${packed}`)).catch(err=>console.log(`有订单失败:${err.message}`));// 2. Promise.race:超时控制constrequest=fetch("/api/data");consttimeout=newPromise((_,reject)=>{setTimeout(()=>reject(newError("请求超时")),5000);});Promise.race([request,timeout]).then(data=>console.log("请求成功",data)).catch(err=>console.log("请求失败",err.message));

七、async/await:Promise的“语法糖”,写异步像写同步

async/await是ES2017新增的语法,基于Promise,让异步代码看起来像同步代码,更易读。

1. 基础用法:

  • async修饰函数,函数返回值自动变成Promise;
  • await只能在async函数内使用,等待Promise完成并返回结果;
  • 错误用try/catch捕获,和同步代码的错误处理一致。
// 用async/await改写之前的流水线asyncfunctionorderMeal(){try{// 等待奶茶做好constmilkTea=awaitorderMilkTea("珍珠奶茶");console.log(`拿到奶茶:${milkTea}`);// 等待蛋糕做好constcake=awaitorderCake("芝士蛋糕");console.log(`拿到蛋糕:${cake}`);// 等待打包constpacked=awaitpackFood([milkTea,cake]);console.log(`打包完成:${packed}`);returnpacked;}catch(err){console.log(`流程失败:${err.message}`);}finally{console.log("流程结束:收起点餐单");}}// 调用async函数(返回Promise)orderMeal();

2. 并发处理:await Promise.all

await配合Promise.all,能实现并发异步任务:

asyncfunctionorderBatch(){try{// 同时发起两个请求,并发执行const[milkTea,cake]=awaitPromise.all([orderMilkTea("珍珠奶茶"),orderCake("芝士蛋糕")]);console.log(`同时拿到:${milkTea}${cake}`);}catch(err){console.log(`失败:${err.message}`);}}

八、避坑指南:Promise的“常见陷阱”

  1. 状态不可逆:一旦resolve/reject,后续再调用resolve/reject无效;
  2. 忘记return Promise:导致链条断裂,下一个then提前执行;
  3. 错误吞噬:嵌套then里的错误,若没catch,会被外层catch捕获,但可能掩盖真正的错误位置;
  4. 同步错误:执行器函数里的同步错误,会直接导致Promise reject,需用try/catch捕获;
  5. 微任务时序:then回调是微任务,会在当前同步代码执行完后、下一轮事件循环前执行(和setTimeout的任务队列不同)。
// 微任务时序示例console.log("1. 同步代码开始");Promise.resolve().then(()=>console.log("2. Promise then(微任务)"));setTimeout(()=>console.log("3. setTimeout(任务队列)"),0);console.log("4. 同步代码结束");// 输出顺序:1 → 4 → 2 → 3

九、总结:Promise的核心价值

Promise的本质是“异步任务的规范化管理工具”,它解决了回调地狱的痛点,提供了清晰的链式流程和统一的错误处理,而async/await让它的用法更接近同步代码。

核心价值总结:

  1. 把“嵌套回调”变成“扁平链式”,可读性提升;
  2. 统一错误处理,避免重复代码;
  3. 支持多个异步任务的协作(并发、竞速等);
  4. 为async/await打下基础,简化异步编程。

Promise是JS异步编程的基石,掌握它的状态机制、链式调用和静态方法,能让你从容应对接口请求、资源加载等各种异步场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/3 4:44:39

Google Play的Keystore不可用时的解决方法

问题描述:在打包google play的app bundle时,需要设置一个上传密钥,并且要自己设置密码。这里常见的不可用的情况有两种,一是keystore找不到了,二是忘记了密码。 影响就是上传的app bundle会提示签名不正确,…

作者头像 李华
网站建设 2026/4/11 19:25:21

如何快速实现Unity游戏自动翻译?XUnity.AutoTranslator终极解决方案

想要让任何Unity游戏瞬间支持中文显示吗?XUnity.AutoTranslator正是你需要的终极翻译工具。这款开源插件专为Unity游戏打造,能够自动识别游戏内文本并实时翻译成你需要的语言,彻底解决游戏语言障碍问题。 【免费下载链接】XUnity.AutoTransla…

作者头像 李华
网站建设 2026/4/3 3:39:18

揭秘:恶意Chrome扩展的常见特征和行为模式

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个Chrome扩展行为分析工具,监控扩展的网络请求、权限使用和DOM修改行为,生成行为报告。使用Electron构建跨平台应用,核心分析模块用Python…

作者头像 李华
网站建设 2026/3/31 6:15:53

LINQ在电商系统中的5个实战应用场景

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 构建一个模拟电商数据处理的ASP.NET Core应用。需求:1) 商品集合的多条件筛选(价格区间、类别);2) 订单数据的GroupBy统计&#xff1…

作者头像 李华
网站建设 2026/4/9 18:43:43

生产环境Helm实战:从零搭建高可用应用发布流水线

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个完整的CI/CD流水线演示项目,展示如何使用Helm在Kubernetes上部署一个包含前端(React)、后端(Spring Boot)和数据库(PostgreSQL)的三层应用。要求:1)…

作者头像 李华
网站建设 2026/4/11 15:25:52

LobeChat错误码对照表:快速定位请求失败原因

LobeChat错误码对照表:快速定位请求失败原因 在现代 AI 应用的开发与部署中,一个看似简单的“请求失败”提示背后,可能隐藏着从网络中断到模型未启动、从密钥过期到插件冲突等数十种不同成因。对于用户而言,“出错了”三个字几乎毫…

作者头像 李华