每日一个知识点:你需要了解的Promise

67 阅读7分钟

首先,需要了解Promise英文意思 :**承诺,**那么核心就来了。

核心思想:一个“承诺”

下面我们来简单分析一下

一、为什么需要 Promise?—— 从“回调地狱”说起

在讲解 Promise 是什么之前,我们必须先知道它解决了什么问题。

想象一个场景:你要做一顿饭,步骤是:

  1. 烧水(需要5分钟)

  2. 水烧开后,煮面(需要3分钟)

  3. 面煮好后,吃面(需要10分钟)

在 JavaScript 的世界里,像烧水、煮面这类需要等待的操作,都是异步操作。如果我们用传统的回调函数(Callback) 来写,代码会变成这样:

烧水(function() {
  煮面(水, function() {
    吃面(面, function() {
      console.log('终于吃完了!');
    });
  });
});

你看,为了确保顺序(水开才能煮面,面好才能吃),我们不得不把函数一层套一层。这仅仅三层嵌套,已经看起来有点乱了。

如果业务更复杂呢?比如吃完面还要洗碗、扔垃圾、看电视……代码就会变成:

烧水(() => {
  煮面(() => {
    吃面(() => {
      洗碗(() => {
        扔垃圾(() => {
          看电视(() => {
            // ... 无限嵌套,形成“金字塔”
          });
        });
      });
    });
  });
});

这种代码被称为 “回调地狱(Callback Hell)”“厄运金字塔” 。它有很多缺点:

  • 难以阅读和维护:缩进越来越多,代码向右“飞”得越来越远。

  • 错误处理困难:很难在每一层都统一处理错误。

哈哈,到这里就说完了 promise 出生原因了,为什么需要它了。时势造英雄

二、Promise 是什么?—— 一个“承诺”

Promise(承诺) 就是一个对象,它代表一个异步操作的最终完成(或失败)及其结果值。

把它想象成现实生活中的“承诺”或“订单”最好理解:

你去快餐店点了一个汉堡。
收银员不会直接把汉堡给你,而是会给你一张小票(Promise)
这张小票就是一个承诺,它承诺你:

  • 最终你会得到汉堡(成功状态,Fulfilled)

  • 或者因为某种原因(比如卖完了)你会得到退款和道歉(失败状态,Rejected)

在等待的过程中,小票的状态是制作中( pending )

拿到小票后,你不用傻傻地等在柜台前。你可以去玩手机(执行其他任务)。当汉堡做好了,小票会提醒你(通过.then()方法)来取餐。

三、Promise 的三种状态

一个 Promise 对象必然处于以下三种状态之一:

  1. pending(等待中):初始状态,既不是成功,也不是失败。

  2. fulfilled(已成功):操作成功完成。

  3. rejected(已失败):操作失败。

状态一旦改变,就凝固了,不会再变

  • 只能从 pending -> fulfilled

  • 或者从 pending -> rejected

具体详解可以从这里查看 :developer.mozilla.org/zh-CN/docs/…

既然知道Promise了,那么现在我们就可以简单使用哈...

四、如何创建一个 Promise?

使用 new Promise() 构造函数,它接受一个函数(执行器)作为参数。这个执行器函数自己有两个参数:resolvereject(这两个也是函数)。

  • resolve(value):当异步操作成功时调用,将状态从 pending 改为 fulfilled,并将结果 value 传递出去。

  • reject(error):当异步操作失败时调用,将状态从 pending 改为 rejected,并将错误信息 error 传递出去。

示例:用 Promise 包装一个简单的异步操作(setTimeout)

const 做汉堡 = new Promise((resolve, reject) => {
  // 模拟制作汉堡需要2秒钟
  setTimeout(() => {
    const 成功 = true; // 假设这次制作成功了
    if (成功) {
      resolve('香喷喷的巨无霸汉堡'); // 承诺达成!传递汉堡给.then
    } else {
      reject('对不起,牛肉卖完了'); // 承诺失败!传递错误信息给.catch
    }
  }, 2000);
});

console.log(做汉堡); // 立即打印,此时状态是 pending

五、如何使用 Promise?—— then、catch、finally

我们拿到“小票”(Promise对象)后,如何知道它成功还是失败,并拿到结果呢?

1. .then() - 处理成功情况

当 Promise 的状态变为 fulfilled 时,.then() 里的回调函数会被调用。

做汉堡.then((得到的汉堡) => {
  console.log(`太好了!我吃到了${得到的汉堡}`);
});
// 2秒后输出:太好了!我吃到了香喷喷的巨无霸汉堡

2. .catch() - 处理失败情况

当 Promise 的状态变为 rejected 时,.catch() 里的回调函数会被调用。这是处理错误推荐的方式

// 修改上面例子,让 const 成功 = false;
做汉堡
  .then((得到的汉堡) => {
    console.log(`太好了!我吃到了${得到的汉堡}`);
  })
  .catch((错误原因) => {
    console.log(`非常失望!原因:${错误原因}`);
  });
// 2秒后输出:非常失望!原因:对不起,牛肉卖完了

3. .finally() - 无论成功失败都会执行

类似于 try...catch 中的 finally,用于执行无论成功还是失败都需要做的清理工作。

做汉堡
  .then((汉堡) => { /* ... */ })
  .catch((错误) => { /* ... */ })
  .finally(() => {
    console.log('无论有没有吃到汉堡,我都要离开柜台了');
  });

接下来,用整合一下Promise的代码用外卖示例下。

// 1. 点外卖这个动作(创建一个Promise)
const orderFood = new Promise((resolve, reject) => {
  // 模拟一个异步操作,比如厨房做饭,需要3秒钟
  console.log("厨师开始做饭...");
  setTimeout(() => {
    const isSuccess = Math.random() > 0.3; // 70%的概率成功

    if (isSuccess) {
      // 成功啦!外卖做好了!
      const meal = "香喷喷的红烧肉";
      resolve(meal); // 将Promise状态从pending变为fulfilled,并传递结果
    } else {
      // 失败啦!没菜了!
      const error = new Error("抱歉,红烧肉卖完了");
      reject(error); // 将Promise状态从pending变为rejected,并传递错误原因
    }
  }, 3000);
});

// 2. 使用这张“小票”(使用Promise)
console.log("我拿到了小票,可以去刷手机了...");

orderFood
  .then((result) => {
    // 成功回调(.then)
    console.log("太棒了!我的:" + result);
  })
  .catch((error) => {
    // 失败回调(.catch)
    console.log("唉!" + error.message);
  })
  .finally(() => {
    // 无论成功失败都会执行(.finally)
    console.log("用餐结束,小票扔了");
  });

运行结果可能有两种:​

  1. ​成功:

    厨师开始做饭... 我拿到了小票,可以去刷手机了... (等待3秒...) 太棒了!我的:香喷喷的红烧肉 用餐结束,小票扔了

  2. 失败:

    厨师开始做饭... 我拿到了小票,可以去刷手机了... (等待3秒...) 唉!抱歉,红烧肉卖完了 用餐结束,小票扔了

人生就是起起落落,不是100%成功,总会有失败,保持好心态。

六、Promise 如何解决“回调地狱”?—— 链式调用(Chaining)

这是 Promise 最强大的特性!.then() 方法总是返回一个新的 Promise,所以你可以继续在后面调用 .then(),形成链式调用。

让我们用 Promise 重写最开始的做饭例子:

// 假设 烧水、煮面、吃面 这些函数都返回一个 Promise

烧水()
  .then(() => {
    console.log('水烧开了!');
    return 煮面(水); // 关键!返回一个新的Promise给下一个.then
  })
  .then(() => {
    console.log('面煮好了!');
    return 吃面(面); // 继续返回一个新的Promise
  })
  .then(() => {
    console.log('终于吃完了!');
  })
  .catch((错误) => {
    // 只需要一个.catch,就能捕获整个链条中任何一步发生的错误!
    console.log('做饭失败了:', 错误);
  });

看!代码变成了从上到下的顺序执行,而不是向右嵌套的“金字塔”,可读性大大增强!

链式调用的秘诀:在 .then() 的回调函数中:

  • 如果你返回一个(比如 return ‘abc’),它会被包装成一个成功的 Promise,传递给下一个 .then

  • 如果你返回一个 Promise(比如 return 煮面(水)),下一个 .then 将等待这个 Promise 解决,并获得它的结果。

  • 如果你抛出错误(比如 throw new Error(‘错了’)),它会触发链中的 .catch

其他常用方法

  • Promise.all([promise1, promise2, ...])

  • ​场景​​:等所有图片都下载完了再一起操作。

  • ​结果​​:只有所有Promise都成功,它才成功(返回一个结果数组);如果有一个失败,整个就失败。

    Promise.all([下载图片1, 下载图片2, 下载图片3]) .then((图片数组) => { console.log('所有图片下载完成:', 图片Array); }) .catch((错误) => { console.log('有张图片下载失败了', 错误); });

  • Promise.race([promise1, promise2, ...])

    • ​场景​​:多家请求,谁先到吃谁家的。

    • ​结果​​:哪个Promise最先改变状态(无论是成功还是失败),它就采用哪个的结果。

    const 数据请求 = fetch('/api/data'); const 超时 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('请求超时')), 5000); });

    Promise.race([数据请求, 超时]) .then((数据) => console.log('数据获取成功', 数据)) .catch((错误) => console.log('错误', 错误)); // 如果5秒内请求没完成,这里会触发超时错误

总结

特性

解释

优点

本质

一个代表异步操作最终结果的对象

将异步操作标准化为对象来处理

状态

pending(进行中)、fulfilled(已成功)、rejected(已失败)

状态不可逆,结果明确

方法

.then() 处理成功,.catch() 处理失败,.finally() 最终处理

错误处理更集中、方便

链式调用

.then() 返回新 Promise,可以连续调用

完美解决回调地狱,代码变为扁平结构

静态方法

Promise.all()(等待所有),Promise.race()(竞速)

处理复杂异步流程控制

Promise 是现代 JavaScript 异步编程的基石,是学习 async/await(一种更简洁的异步语法,其底层就是 Promise)的必备基础。希望今天这个讲解对你有帮助!

参考资料: developer.mozilla.org/zh-CN/docs/…