简单了解 Promise

374 阅读8分钟

1. 什么是 Promise?

Promise 是 JavaScript 中处理异步操作的一种模式。它是一个对象,代表了一个尚未完成但预期将来会完成的操作的结果。简单来说,它像一个承诺:现在我可能无法立即给你结果,但我承诺将来会给你一个结果(要么成功,要么失败)。

主要解决的问题:

  • 回调地狱 (Callback Hell): 避免多层嵌套的回调函数,使代码更易读、更易维护。
  • 异步流程控制: 提供更清晰、更强大的异步操作管理方式。

2. Promise 的三种状态

一个 Promise 实例必然处于以下三种状态之一:

  • Pending (进行中): 初始状态,异步操作正在进行中,尚未完成(既未成功也未失败)。
  • Fulfilled (已成功): 异步操作成功完成。此时 Promise 有一个最终的值 (value)。
  • Rejected (已失败): 异步操作失败。此时 Promise 有一个原因 (reason),通常是一个 Error 对象。

状态转换:

  • Promise 的状态只能从 Pending 变为 FulfilledRejected
  • 一旦状态改变(变为 FulfilledRejected),就不能再改变了。这个过程称为 "settled"(已敲定)。

3. 创建 Promise

我们使用 new Promise() 构造函数来创建一个 Promise 实例。构造函数接收一个函数作为参数,这个函数被称为 "executor"。

Executor 函数会立即执行,并接收两个参数:resolvereject。这两个参数本身也是函数。

  • resolve(value): 当异步操作成功时调用,将 Promise 的状态从 Pending 变为 Fulfilled,并将 value 作为结果传递出去。
  • reject(reason): 当异步操作失败时调用,将 Promise 的状态从 Pending 变为 Rejected,并将 reason (通常是错误对象) 作为原因传递出去。

示例 1: 创建一个简单的 Promise

// 创建一个模拟异步操作的 Promise
const myFirstPromise = new Promise((resolve, reject) => {
  console.log("Promise 开始执行 (executor)...");
  // 模拟一个耗时操作,例如网络请求或文件读取
  setTimeout(() => {
    const success = Math.random() > 0.5; // 随机决定成功或失败
    if (success) {
      console.log("异步操作成功!");
      resolve("操作成功完成的数据"); // 操作成功,调用 resolve
    } else {
      console.error("异步操作失败!");
      reject(new Error("操作失败的原因")); // 操作失败,调用 reject
    }
  }, 2000); // 模拟 2 秒延迟
});

console.log("Promise 已创建 (同步代码)");

4. 消费 Promise (使用 .then(), .catch(), .finally())

创建了 Promise 之后,我们需要一种方式来获取它的最终结果(成功的值或失败的原因)。这通过 Promise 实例的 .then(), .catch(), 和 .finally() 方法来实现。

  • .then(onFulfilled, onRejected):

    • onFulfilled (可选): 当 Promise 状态变为 Fulfilled 时调用的函数。它接收 Promise 成功时的值作为参数。
    • onRejected (可选): 当 Promise 状态变为 Rejected 时调用的函数。它接收 Promise 失败时的原因作为参数。
    • .then() 方法返回一个新的 Promise,这使得链式调用成为可能。
  • .catch(onRejected):

    • .then(null, onRejected) 的语法糖,专门用于处理 Promise 被拒绝 (Rejected) 的情况。
    • 它也返回一个新的 Promise。
  • .finally(onFinally):

    • 无论 Promise 最终是 Fulfilled 还是 RejectedonFinally 函数都会被执行。
    • 它不接收任何参数,通常用于执行一些清理工作(例如隐藏加载指示器)。
    • 它也返回一个新的 Promise。

示例 2: 使用 .then().catch() 处理 Promise 结果

// 接上一个示例 myFirstPromise

console.log("准备处理 Promise 结果...");

myFirstPromise
  .then((successValue) => {
    // 当 myFirstPromise 状态变为 Fulfilled 时执行
    console.log("接收到成功结果:", successValue);
    // 这里可以对成功结果进行进一步处理
    return "处理后的成功数据"; // then 方法可以返回新的值,传递给下一个 then
  })
  .catch((errorReason) => {
    // 当 myFirstPromise 状态变为 Rejected 时执行
    console.error("捕获到错误:", errorReason);
    // 这里可以处理错误,例如向用户显示错误信息
    // catch 也可以抛出新的错误或返回一个值(会被视为成功)
    // throw new Error("在 catch 中抛出新错误");
    return "错误已被处理"; // 如果 catch 返回一个值,后续的 then 会执行
  })
  .then((finalResult) => {
      // 这个 then 会接收上一个 then 或 catch 的返回值
      console.log("链式调用的最终结果:", finalResult);
  })
  .finally(() => {
    // 无论成功还是失败,都会执行
    console.log("Promise 处理流程结束 (finally)。");
  });

console.log("Promise 处理逻辑已设置 (同步代码)");

运行上述代码可能的输出 (成功情况):

Promise 开始执行 (executor)...
Promise 已创建 (同步代码)
准备处理 Promise 结果...
Promise 处理逻辑已设置 (同步代码)
// --- 等待 2 秒 ---
异步操作成功!
接收到成功结果: 操作成功完成的数据
链式调用的最终结果: 处理后的成功数据
Promise 处理流程结束 (finally)。

运行上述代码可能的输出 (失败情况):

Promise 开始执行 (executor)...
Promise 已创建 (同步代码)
准备处理 Promise 结果...
Promise 处理逻辑已设置 (同步代码)
// --- 等待 2 秒 ---
异步操作失败!
捕获到错误: 操作失败的原因
链式调用的最终结果: 错误已被处理
Promise 处理流程结束 (finally)。

5. Promise 链式调用

.then().catch() 都会返回一个新的 Promise。这使得我们可以将多个异步操作链接起来,形成一个清晰的执行序列。

  • 前一个 .then()onFulfilled 回调函数的返回值,会作为下一个 .then()onFulfilled 回调函数的参数。
  • 如果在任何一个 .then() 中抛出错误,或者返回一个状态为 Rejected 的 Promise,那么链条会中断,直接跳到最近的 .catch()
  • .catch() 也可以返回值,这个值会被传递给后续的 .then()

示例 3: Promise 链式调用

function stepOne() {
  return new Promise((resolve) => {
    console.log("步骤一: 开始");
    setTimeout(() => {
      console.log("步骤一: 完成");
      resolve(1); // 返回第一步的结果
    }, 1000);
  });
}

function stepTwo(previousResult) {
  return new Promise((resolve) => {
    console.log("步骤二: 开始,接收到上一步结果:", previousResult);
    setTimeout(() => {
      console.log("步骤二: 完成");
      resolve(previousResult + 1); // 基于上一步结果进行计算
    }, 1000);
  });
}

function stepThree(previousResult) {
  return new Promise((resolve, reject) => {
    console.log("步骤三: 开始,接收到上一步结果:", previousResult);
    setTimeout(() => {
      if (previousResult > 2) {
          console.log("步骤三: 完成");
          resolve("最终成功!");
      } else {
          console.error("步骤三: 失败");
          reject(new Error("步骤三条件不满足"));
      }
    }, 1000);
  });
}

console.log("开始 Promise 链...");

stepOne()
  .then(stepTwo) // 将 stepTwo 函数直接传递给 then
  .then(stepThree)
  .then((finalSuccessMessage) => {
    console.log("整个流程成功:", finalSuccessMessage);
  })
  .catch((error) => {
    console.error("流程中出现错误:", error.message);
  })
  .finally(() => {
    console.log("Promise 链执行完毕。");
  });

console.log("Promise 链已启动 (同步代码)");

6. Promise 静态方法

Promise 对象还提供了一些静态方法,用于处理多个 Promise:

  • Promise.all(iterable):

    • 接收一个 Promise 数组(或可迭代对象)。
    • 所有 Promise 都成功 (Fulfilled) 时,它才成功,返回值是所有 Promise 结果组成的数组(按传入顺序)。
    • 只要有一个 Promise 失败 (Rejected),它就立即失败,返回值是第一个失败 Promise 的原因。
    • 常用于需要等待多个并行异步操作都完成的场景。
  • Promise.race(iterable):

    • 接收一个 Promise 数组。
    • 只要任意一个 Promise 状态改变(无论是 Fulfilled 还是 Rejected),它就立即采用那个 Promise 的状态和结果/原因。
    • 像赛跑一样,谁先完成(或失败)就以谁为准。
  • Promise.allSettled(iterable):

    • 接收一个 Promise 数组。
    • 等待所有 Promise 都已敲定(settled,即无论是 Fulfilled 还是 Rejected)。
    • 它总是成功 (Fulfilled),返回值是一个对象数组,每个对象描述了对应 Promise 的最终状态 (status: 'fulfilled' 或 'rejected') 和结果 (value) 或原因 (reason)。
    • 适用于不关心某个异步操作是否失败,但需要知道所有操作都已完成的场景。
  • Promise.any(iterable):

    • 接收一个 Promise 数组。
    • 只要有一个 Promise 成功 (Fulfilled),它就立即成功,返回值是第一个成功 Promise 的结果。
    • 只有当所有 Promise 都失败 (Rejected) 时,它才失败,返回值是一个 AggregateError 对象,包含了所有失败的原因。

示例 4: 使用 Promise.all()

const promise1 = Promise.resolve(3); // 一个立即成功的 Promise
const promise2 = 42; // 非 Promise 值会被视为立即成功的 Promise
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo'); // 延迟 100ms 成功的 Promise
});
const promise4 = new Promise((resolve, reject) => {
  setTimeout(reject, 50, 'bar error'); // 延迟 50ms 失败的 Promise
});

// 场景一:所有 Promise 都成功
Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log("Promise.all 成功:", values); // 输出: [3, 42, "foo"]
  })
  .catch((error) => {
    console.error("Promise.all 失败:", error);
  });

// 场景二:有 Promise 失败
Promise.all([promise1, promise2, promise4])
  .then((values) => {
    console.log("Promise.all 成功:", values);
  })
  .catch((error) => {
    console.error("Promise.all 失败:", error); // 输出: bar error
  });

7. 与 async/await 的关系

async/await 是 ES2017 引入的语法糖,它建立在 Promise 之上,使得异步代码的书写方式更接近同步代码,提高了可读性。

  • async 函数总是隐式地返回一个 Promise。
  • await 操作符只能在 async 函数内部使用,它会暂停 async 函数的执行,等待后面的 Promise 完成(settled)。
    • 如果 Promise 成功 (Fulfilled),await 返回 Promise 的结果值。
    • 如果 Promise 失败 (Rejected),await 会抛出 Promise 的错误原因(需要使用 try...catch 来捕获)。

示例 5: 使用 async/await 改写示例 3

// 假设 stepOne, stepTwo, stepThree 函数定义同上

async function runSteps() {
  console.log("开始 async/await 流程...");
  try {
    const result1 = await stepOne();
    const result2 = await stepTwo(result1);
    const finalResult = await stepThree(result2);
    console.log("整个流程成功 (async/await):", finalResult);
  } catch (error) {
    console.error("流程中出现错误 (async/await):", error.message);
  } finally {
    console.log("async/await 流程执行完毕。");
  }
}

runSteps();
console.log("async/await 流程已启动 (同步代码)");

这个 async/await 版本与示例 3 的 Promise 链实现了相同的功能,但代码结构看起来更像传统的同步代码。