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}`);
    return orderCake("芝士蛋糕"); // 返回新的Promise,衔接下一个任务
  })
  .then(cake => {
    console.log(`拿到${cake}`);
    return packFood([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函数
function orderMilkTea(type) {
  // 执行器函数:立即执行,里面包含异步任务
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 模拟异步操作:1秒后完成
      const hasIngredient = Math.random() > 0.3; // 70%概率有食材
      if (hasIngredient) {
        // 成功:调用resolve,传递结果
        resolve(`${type}(少糖少冰)`);
      } else {
        // 失败:调用reject,传递错误原因
        reject(new Error(`${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}`);
    return orderCake("芝士蛋糕"); // 返回新Promise,衔接下一个任务
  })
  .then(cake => {
    console.log(`拿到蛋糕:${cake}`);
    return packFood([milkTea, cake]); // 继续返回Promise
  })
  .then(packed => console.log(`打包完成:${packed}`))
  .catch(err => console.log(`流程失败:${err.message}`));

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

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

// 反面例子:忘记return,fetch是漂浮Promise
orderMilkTea("珍珠奶茶")
  .then(milkTea => {
    fetch("/api/recordOrder"); // 没返回,无法追踪结果
  })
  .then(() => {
    console.log("订单记录完成"); // 可能在fetch完成前执行
  });

// 正面例子:返回Promise,确保顺序
orderMilkTea("珍珠奶茶")
  .then(milkTea => {
    return fetch("/api/recordOrder"); // 返回Promise,下一个then等待它完成
  })
  .then(() => {
    console.log("订单记录完成"); // 正确:fetch完成后执行
  });

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

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

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

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

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

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

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

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

3. catch后的链式恢复

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

orderMilkTea("珍珠奶茶")
  .then(milkTea => {
    throw new Error("奶茶太甜了");
  })
  .catch(err => {
    console.log(`处理错误:${err.message},换无糖的`);
    return orderMilkTea("珍珠奶茶(无糖)"); // 补救,返回新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:全部成功才返回
const p1 = orderMilkTea("珍珠奶茶");
const p2 = 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:超时控制
const request = fetch("/api/data");
const timeout = new Promise((_, reject) => {
  setTimeout(() => reject(new Error("请求超时")), 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改写之前的流水线
async function orderMeal() {
  try {
    // 等待奶茶做好
    const milkTea = await orderMilkTea("珍珠奶茶");
    console.log(`拿到奶茶:${milkTea}`);
    
    // 等待蛋糕做好
    const cake = await orderCake("芝士蛋糕");
    console.log(`拿到蛋糕:${cake}`);
    
    // 等待打包
    const packed = await packFood([milkTea, cake]);
    console.log(`打包完成:${packed}`);
    
    return packed;
  } catch (err) {
    console.log(`流程失败:${err.message}`);
  } finally {
    console.log("流程结束:收起点餐单");
  }
}

// 调用async函数(返回Promise)
orderMeal();

2. 并发处理:await Promise.all

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

async function orderBatch() {
  try {
    // 同时发起两个请求,并发执行
    const [milkTea, cake] = await Promise.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异步编程的基石,掌握它的状态机制、链式调用和静态方法,能让你从容应对接口请求、资源加载等各种异步场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿蒙Armon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值