摘要

本文系统讲解 JavaScript Promise 核心原理、状态流转、常用 API 及实战场景,拆解回调地狱的解决方案,手把手实现符合 Promise/A+ 规范的核心版 Promise,结合大量可运行案例,覆盖日常开发与高频面试考点,帮助开发者彻底掌握异步编程基础,摆脱回调嵌套困境。


一、前言:为什么需要 Promise?

在 Promise 出现之前,JavaScript 异步编程完全依赖回调函数,当存在多层异步嵌套时,会出现 “回调地狱(Callback Hell)”,代码可读性、维护性极差,难以调试。

回调地狱痛点示例(真实场景)

javascript

运行

// 多层异步嵌套:获取用户信息 → 获取用户订单 → 获取订单详情
getUserInfo(userId, function(user) {
  getUserOrder(user.id, function(order) {
    getOrderDetail(order.id, function(detail) {
      console.log(detail); // 嵌套3层已混乱,嵌套10层直接无法维护
    }, function(err) { console.log(err); });
  }, function(err) { console.log(err); });
}, function(err) { console.log(err); });

Promise 解决的核心问题

  1. 解决回调地狱,实现异步代码扁平化
  2. 统一异步错误处理,避免重复写错误回调
  3. 支持异步操作串行、并行控制
  4. 提供标准化的异步编程规范(Promise/A+ 规范)

二、Promise 核心概念

1. 定义

Promise 是一个异步操作的容器,用于表示一个异步操作的最终完成(成功)或失败(拒绝),并返回其结果值。

2. 核心特性

  • Promise 有且只有三种状态,状态一旦改变,无法逆转(面试必记)
    1. pending:初始状态,异步操作未完成
    2. fulfilled:成功状态,异步操作完成,返回结果
    3. rejected:失败状态,异步操作失败,返回错误信息
  • 状态流转:pending → fulfilledpending → rejected(只能二选一)
  • 支持链式调用(then 方法),彻底摆脱回调嵌套

3. 基础语法(必背)

javascript

运行

// 1. 创建 Promise 实例
const promise = new Promise((resolve, reject) => {
  // 异步操作(如接口请求、定时器)
  setTimeout(() => {
    const success = true;
    if (success) {
      // 成功:调用 resolve,传入结果
      resolve("操作成功,返回数据");
    } else {
      // 失败:调用 reject,传入错误信息
      reject(new Error("操作失败"));
    }
  }, 1000);
});

// 2. 调用 then 方法,处理成功/失败
promise.then(
  (res) => {
    console.log("成功:", res); // 接收 resolve 传入的值
  },
  (err) => {
    console.log("失败:", err); // 接收 reject 传入的错误
  }
);

三、Promise 常用 API 详解(实战必用)

1. then 方法(核心)

  • 作用:注册异步操作的成功 / 失败回调
  • 特点:返回一个新的 Promise,支持链式调用
  • 注意:then 回调是微任务(后续第 44 篇详解)

javascript

运行

// 链式调用示例(解决回调地狱)
new Promise((resolve) => {
  setTimeout(() => resolve(1), 1000);
})
.then((res) => {
  console.log(res); // 1
  return res + 1; // 返回值会作为下一个 then 的参数
})
.then((res) => {
  console.log(res); // 2
  return new Promise((resolve) => resolve(res + 1)); // 可返回 Promise
})
.then((res) => {
  console.log(res); // 3
});

2. catch 方法(错误处理)

  • 作用:专门处理 Promise 失败状态(替代 then 的第二个参数,更规范)
  • 特点:捕获链式调用中所有前面的错误(包括 then 中的错误)

javascript

运行

new Promise((resolve, reject) => {
  reject(new Error("初始错误"));
})
.then((res) => {
  console.log(res);
})
.catch((err) => {
  console.log("捕获错误:", err.message); // 捕获初始错误
});

// 链式错误传递
new Promise((resolve) => resolve(1))
.then((res) => {
  throw new Error("then 中的错误"); // 手动抛出错误
})
.then((res) => console.log(res))
.catch((err) => {
  console.log("捕获错误:", err.message); // 捕获 then 中的错误
});

3. finally 方法(收尾操作)

  • 作用:无论 Promise 成功还是失败,都会执行(如关闭加载 loading)
  • 特点:不接收参数,不改变 Promise 的状态和结果

javascript

运行

new Promise((resolve, reject) => {
  setTimeout(() => resolve("成功"), 1000);
})
.then((res) => console.log(res))
.catch((err) => console.log(err))
.finally(() => {
  console.log("无论成功失败,都会执行(如关闭loading)");
});

4. 静态方法(基础)

  • Promise.resolve(value):快速创建一个成功状态的 Promise
  • Promise.reject(error):快速创建一个失败状态的 Promise

javascript

运行

// 快速创建成功 Promise
Promise.resolve(100).then((res) => console.log(res)); // 100

// 快速创建失败 Promise
Promise.reject(new Error("失败")).catch((err) => console.log(err));

四、Promise 状态流转(面试必画)

1. 状态流转图(文字版,面试可口述)

plaintext

pending(初始)
  ↗️          ↘️
fulfilled(成功)  rejected(失败)
  ↓                ↓
then 回调执行      catch 回调执行
  ↓                ↓
返回新 Promise     返回新 Promise

2. 关键注意点(面试避坑)

  • 状态一旦改变(pending → fulfilled/rejected),无法再次改变
  • 即使 Promise 已经成功 / 失败,再调用 resolve/reject 也无效
  • then/catch/finally 都会返回新的 Promise,因此可以链式调用
  • 未捕获的 Promise 错误,会触发浏览器 Uncaught Error

五、手写 Promise(核心版,面试必写)

核心思路(Promise/A+ 规范核心)

  1. 定义三种状态:pending、fulfilled、rejected
  2. 接收 executor 函数,立即执行,传入 resolve、reject 方法
  3. resolve 方法:将状态改为 fulfilled,保存成功结果,执行成功回调队列
  4. reject 方法:将状态改为 rejected,保存错误信息,执行失败回调队列
  5. then 方法:注册回调,若状态已改变,直接执行对应回调

手写代码(可直接运行,面试简化版)

javascript

运行

// 手写 Promise(符合 Promise/A+ 核心规范)
class MyPromise {
  // 1. 初始化状态和结果
  constructor(executor) {
    this.status = "pending"; // 初始状态
    this.value = undefined; // 成功结果
    this.reason = undefined; // 失败原因

    // 2. resolve 方法:修改状态为成功,保存结果
    const resolve = (value) => {
      if (this.status === "pending") {
        this.status = "fulfilled";
        this.value = value;
      }
    };

    // 3. reject 方法:修改状态为失败,保存错误
    const reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected";
        this.reason = reason;
      }
    };

    // 4. 执行 executor,捕获同步错误
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  // 5. then 方法:注册回调
  then(onFulfilled, onRejected) {
    // 成功回调执行
    if (this.status === "fulfilled") {
      onFulfilled(this.value);
    }
    // 失败回调执行
    if (this.status === "rejected") {
      onRejected(this.reason);
    }
  }
}

// 测试手写 Promise
new MyPromise((resolve, reject) => {
  setTimeout(() => resolve("手写 Promise 成功"), 1000);
}).then(
  (res) => console.log(res),
  (err) => console.log(err)
);

面试加分点

  • 补充 “回调队列”(处理异步 then 回调),可升级为 “完整版手写”
  • 说明:手写版本无需完全符合 Promise/A+ 所有细节,核心逻辑正确即可

六、Promise 实战场景(真实项目常用)

场景 1:接口请求封装(结合 axios)

javascript

运行

// 用 Promise 封装接口请求
function request(url, method = "GET") {
  return new Promise((resolve, reject) => {
    axios({ url, method })
      .then((res) => resolve(res.data))
      .catch((err) => reject(err));
  });
}

// 使用(链式调用,无嵌套)
request("/api/user")
.then((user) => request(`/api/order?userId=${user.id}`))
.then((order) => request(`/api/orderDetail?orderId=${order.id}`))
.then((detail) => console.log(detail))
.catch((err) => console.log("请求错误:", err));

场景 2:异步操作串行执行

javascript

运行

// 需求:先获取用户,再获取订单,再获取详情(串行)
function getUser() {
  return Promise.resolve({ id: 1, name: "张三" });
}
function getOrder(userId) {
  return Promise.resolve({ id: 101, userId });
}
function getDetail(orderId) {
  return Promise.resolve({ id: 1001, orderId, goods: "手机" });
}

// 串行执行
getUser()
.then((user) => getOrder(user.id))
.then((order) => getDetail(order.id))
.then((detail) => console.log(detail))
.catch((err) => console.log(err));

七、高频面试题(必背)

  1. Promise 有哪三种状态?状态可以逆转吗?答:pending(初始)、fulfilled(成功)、rejected(失败);状态一旦改变,无法逆转。

  2. Promise.then 为什么能链式调用?答:then 方法会返回一个新的 Promise,因此可以连续调用 then。

  3. catch 能捕获哪些错误?答:捕获前面所有 Promise 的 rejected 状态,以及 then 回调中抛出的错误。

  4. Promise.resolve 和 new Promise (resolve => resolve ()) 有区别吗?答:基本无区别,前者是后者的语法糖,更简洁。

  5. 手写 Promise 的核心要点是什么?答:定义三种状态、实现 resolve/reject 改变状态、then 方法注册回调。


八、总结

  1. Promise 是异步编程的标准化解决方案,解决回调地狱
  2. 三种状态不可逆转,pending → fulfilled 或 pending → rejected
  3. then/catch/finally 支持链式调用,实现异步操作扁平化
  4. 手写 Promise 核心:状态管理 + resolve/reject + then 回调注册
  5. 是后续 async/await、宏任务 / 微任务的基础,面试必考
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐