news 2026/1/30 21:13:20

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/1/29 13:04:40

手写海康OpenApi签名规范,实现手动调用api(sdk:artemis-http-client)

1. 前言: artemis-http-client sdk 中提供获取门禁事件图片的方法,但实际图片访问地址为该响应的重定向地址 问题来了:虽然他提供了 sdk ,但没有办法通过 sdk 获取重定向的地址于是产生了本文,自己通过hutools的 httpU…

作者头像 李华
网站建设 2026/1/29 10:35:04

NVIDIA TensorRT-LLM高性能推理框架解析

NVIDIA TensorRT-LLM高性能推理框架解析 在大语言模型(LLMs)加速落地的今天,一个现实问题摆在所有AI工程团队面前:如何让百亿参数的模型不仅“能跑”,还要“跑得快、成本低、稳得住”?PyTorch 虽然训练灵活…

作者头像 李华
网站建设 2026/1/29 14:33:36

Git补丁零基础入门:从创建到应用的完整指南

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个交互式Git补丁学习工具,包含:1)可视化补丁创建向导 2)实时演练环境 3)常见错误模拟与修正 4)渐进式难度练习 5)即时反馈系统。要求界面友好&#xf…

作者头像 李华
网站建设 2026/1/29 11:52:11

零基础教程:20分钟用Llama Factory训练你的第一个AI模型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建新手友好型训练向导:1.使用电影评论数据集 2.分步指导数据上传 3.自动选择情感分析预设模板 4.简化参数配置界面 5.生成训练进度可视化看板。要求每个步骤都有示意图…

作者头像 李华
网站建设 2026/1/29 15:00:42

给文科生看的Kubernetes:用快递系统理解容器编排

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个面向初学者的Kubernetes交互式学习项目,包含:1) 用快递系统类比Pod/Node/Service的动画演示 2) 可视化命令行模拟器 3) 安全沙箱环境 4) 渐进式难度…

作者头像 李华
网站建设 2026/1/29 12:30:34

效率对比:5种Ubuntu安装Node.js方法耗时测评

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 请生成一个自动化测试脚本,比较以下5种Node.js安装方法在Ubuntu 22.04上的耗时:1.apt官方源 2.NodeSource源 3.nvm安装 4.二进制包安装 5.Docker容器。脚本应…

作者头像 李华