ES6——Promise

一文彻底搞懂 Promise:从原理到手写实现,面试再也不怕

Promise 是 ES6 引入的最重要的特性之一,彻底解决了 JavaScript 异步编程的回调地狱问题,是现代前端开发的基石。它不仅是日常开发中最常用的工具,更是前端面试的必考题

本文将从回调地狱→核心原理→完整API→手写实现→常见坑点→真实应用六个维度,带你彻底搞懂 Promise,让你不仅会用,更能在面试中从容应对所有相关问题。


一、为什么我们需要 Promise?

在 Promise 出现之前,JavaScript 处理异步任务只能用回调函数 。当异步任务越来越多,就会形成臭名昭著的回调地狱(Callback Hell)

回调地狱的噩梦

javascript 复制代码
// 需求:先获取用户信息,再根据用户ID获取订单,再根据订单ID获取商品
getUserInfo(userId, function(user) {
  getOrderList(user.id, function(orders) {
    getGoodsList(orders[0].id, function(goods) {
      console.log('最终获取到商品:', goods);
      
      // 如果还有更多异步任务...
      // 代码会不断向右缩进,越来越难维护
    }, function(err) {
      console.error('获取商品失败:', err);
    });
  }, function(err) {
    console.error('获取订单失败:', err);
  });
}, function(err) {
  console.error('获取用户失败:', err);
});

回调地狱的问题:

  1. 代码可读性差:层层嵌套,难以理解和维护
  2. 错误处理困难:每个回调都要单独处理错误
  3. 无法并行执行:多个异步任务只能串行执行,效率低下
  4. 无法返回值:异步任务的结果只能在回调内部使用

Promise 就是为了解决这些问题而生的。它将异步操作的结果和处理逻辑分离开,用链式调用的方式替代了嵌套回调,让异步代码变得像同步代码一样清晰易读。


二、Promise 核心定义与特性

1. 什么是 Promise?

Promise 是一个表示异步操作最终完成或失败的对象 。它本质上是一个状态机,用来管理异步操作的状态和结果。

你可以把 Promise 理解为一个承诺

我承诺会在未来某个时间完成一件事,完成后我会通知你,你可以根据结果做相应的处理。

2. Promise 的三个状态

Promise 有且只有三个状态:

  • pending(进行中):初始状态,异步操作正在执行
  • fulfilled(已成功):异步操作成功完成
  • rejected(已失败):异步操作失败

3. 最核心的特性:状态不可逆

Promise 的状态一旦从 pending 变为 fulfilledrejected,就永远不会再改变

javascript 复制代码
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    resolve('成功'); // 状态变为 fulfilled
    reject('失败'); // 这行代码永远不会执行,状态已经改变
  }, 1000);
});

三、Promise 基本 API

1. 创建 Promise 实例

javascript 复制代码
const promise = new Promise((resolve, reject) => {
  // 这里写异步操作代码
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('操作成功'); // 成功时调用 resolve,状态变为 fulfilled
    } else {
      reject(new Error('操作失败')); // 失败时调用 reject,状态变为 rejected
    }
  }, 1000);
});

2. Promise.prototype.then()

then 方法是 Promise 最核心的方法,用来注册异步操作成功和失败的回调函数。

javascript 复制代码
promise.then(
  (result) => {
    // 成功回调,resolve 时执行
    console.log(result); // "操作成功"
  },
  (error) => {
    // 失败回调,reject 时执行
    console.error(error);
  }
);

最重要的特性:链式调用 then 方法会返回一个新的 Promise 实例,所以可以链式调用:

javascript 复制代码
getUserInfo(userId)
  .then(user => getOrderList(user.id))
  .then(orders => getGoodsList(orders[0].id))
  .then(goods => console.log('最终获取到商品:', goods))
  .catch(err => console.error('任何一步出错都会在这里捕获:', err));

这就是 Promise 解决回调地狱的关键:用链式调用 替代了嵌套回调

3. Promise.prototype.catch()

catch 方法用来捕获 Promise 链中任何一个环节的错误,相当于 then(null, errorCallback) 的语法糖。

javascript 复制代码
// 推荐写法:用 catch 统一处理错误
getUserInfo(userId)
  .then(user => getOrderList(user.id))
  .then(orders => getGoodsList(orders[0].id))
  .then(goods => console.log(goods))
  .catch(err => console.error('出错了:', err));

4. Promise.prototype.finally()

finally 方法不管 Promise 最终是成功还是失败,都会执行。通常用来做一些清理工作,比如关闭加载动画。

javascript 复制代码
showLoading();
getUserInfo(userId)
  .then(user => console.log(user))
  .catch(err => console.error(err))
  .finally(() => {
    hideLoading(); // 无论成功失败,都会关闭加载动画
  });

四、Promise 静态 API(面试必背)

Promise 提供了 6 个常用的静态方法,用来处理多个 Promise 实例。

1. Promise.resolve()

快速创建一个已经成功的 Promise 实例。

javascript 复制代码
// 等价于 new Promise(resolve => resolve('成功'))
const promise = Promise.resolve('成功');

2. Promise.reject()

快速创建一个已经失败的 Promise 实例。

javascript 复制代码
// 等价于 new Promise((resolve, reject) => reject(new Error('失败')))
const promise = Promise.reject(new Error('失败'));

3. Promise.all()

所有 Promise 都成功才成功,只要有一个失败就立即失败

javascript 复制代码
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
  .then(results => {
    console.log(results); // [1, 2, 3] 结果顺序和传入顺序一致
  })
  .catch(err => {
    console.error(err); // 只要有一个失败,就会进入 catch
  });

适用场景:多个异步任务并行执行,需要等待所有任务完成后再处理结果。

4. Promise.race()

谁先完成就返回谁的结果,不管成功还是失败

javascript 复制代码
const p1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 500));

Promise.race([p1, p2])
  .then(result => {
    console.log(result); // 2,因为 p2 先完成
  });

适用场景:请求超时控制。

javascript 复制代码
// 给请求设置 3 秒超时
const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('请求超时')), 3000);
});

Promise.race([fetch('/api/data'), timeout])
  .then(res => res.json())
  .catch(err => console.error(err));

5. Promise.allSettled()

等待所有 Promise 都完成(不管成功还是失败),返回所有结果

javascript 复制代码
const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error('失败'));
const p3 = Promise.resolve(3);

Promise.allSettled([p1, p2, p3])
  .then(results => {
    console.log(results);
    // [
    //   { status: 'fulfilled', value: 1 },
    //   { status: 'rejected', reason: Error: 失败 },
    //   { status: 'fulfilled', value: 3 }
    // ]
  });

适用场景:需要知道所有异步任务的最终状态,不管成功还是失败。

6. Promise.any()

只要有一个 Promise 成功就成功,所有都失败才失败

javascript 复制代码
const p1 = Promise.reject(new Error('失败1'));
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.any([p1, p2, p3])
  .then(result => {
    console.log(result); // 2,第一个成功的结果
  });

适用场景:多个备用接口,只要有一个成功就可以。

静态方法对比表

方法 成功条件 失败条件 返回结果
Promise.all() 所有都成功 任意一个失败 所有成功结果的数组
Promise.race() 任意一个先完成 任意一个先失败 第一个完成的结果
Promise.allSettled() 永远成功 永远不会失败 所有结果的数组
Promise.any() 任意一个成功 所有都失败 第一个成功的结果

五、手写 Promise(面试必考题)

手写 Promise 是前端面试的高频考点,下面我们一步步实现一个符合 Promise/A+ 规范的 Promise。

最简版 Promise

javascript 复制代码
class MyPromise {
  constructor(executor) {
    // 初始状态
    this.status = 'pending';
    // 成功结果
    this.value = undefined;
    // 失败原因
    this.reason = undefined;

    // 成功回调
    const resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.value = value;
      }
    };

    // 失败回调
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.reason = reason;
      }
    };

    // 执行传入的执行器函数
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  // then 方法
  then(onFulfilled, onRejected) {
    if (this.status === 'fulfilled') {
      onFulfilled(this.value);
    }
    if (this.status === 'rejected') {
      onRejected(this.reason);
    }
  }
}

完善版 Promise(支持异步和链式调用)

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    // 存储成功回调队列
    this.onFulfilledCallbacks = [];
    // 存储失败回调队列
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.value = value;
        // 执行所有成功回调
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.reason = reason;
        // 执行所有失败回调
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    // 处理回调函数默认值
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    // 返回新的 Promise,实现链式调用
    return new MyPromise((resolve, reject) => {
      if (this.status === 'fulfilled') {
        setTimeout(() => {
          try {
            const result = onFulfilled(this.value);
            resolve(result);
          } catch (err) {
            reject(err);
          }
        }, 0);
      }

      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            resolve(result);
          } catch (err) {
            reject(err);
          }
        }, 0);
      }

      if (this.status === 'pending') {
        // 异步情况,将回调存入队列
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onFulfilled(this.value);
              resolve(result);
            } catch (err) {
              reject(err);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onRejected(this.reason);
              resolve(result);
            } catch (err) {
              reject(err);
            }
          }, 0);
        });
      }
    });
  }

  // catch 方法
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  // finally 方法
  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),
      reason => MyPromise.resolve(callback()).then(() => { throw reason })
    );
  }

  // 静态方法 resolve
  static resolve(value) {
    return new MyPromise(resolve => resolve(value));
  }

  // 静态方法 reject
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason));
  }

  // 静态方法 all
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      let count = 0;
      for (let i = 0; i < promises.length; i++) {
        MyPromise.resolve(promises[i]).then(
          value => {
            results[i] = value;
            count++;
            if (count === promises.length) {
              resolve(results);
            }
          },
          reason => reject(reason)
        );
      }
    });
  }
}

六、Promise 常见坑点与误区

1. Promise 是立即执行的

javascript 复制代码
console.log('开始');
const promise = new Promise((resolve, reject) => {
  console.log('Promise 执行器执行');
  resolve('成功');
});
console.log('结束');

// 输出顺序:开始 → Promise 执行器执行 → 结束

Promise 的执行器函数会立即同步执行 ,只有 then 里的回调是异步的。

2. then 回调是微任务

Promise 的 thencatchfinally 回调都是微任务,会在当前宏任务执行完毕后、下一个宏任务开始前执行。

javascript 复制代码
console.log('宏任务1');
setTimeout(() => {
  console.log('宏任务2');
}, 0);
Promise.resolve().then(() => {
  console.log('微任务1');
});
console.log('宏任务3');

// 输出顺序:宏任务1 → 宏任务3 → 微任务1 → 宏任务2

3. 错误冒泡特性

Promise 链中的错误会一直向后冒泡,直到被 catch 捕获。

javascript 复制代码
Promise.resolve()
  .then(() => {
    throw new Error('第一步出错');
  })
  .then(() => {
    console.log('这行永远不会执行');
  })
  .catch(err => {
    console.error('捕获到错误:', err); // 会在这里捕获到第一步的错误
  });

4. 不要在 Promise 里写 return new Promise

很多人会犯的错误:

javascript 复制代码
// ❌ 多余的包装
function getData() {
  return new Promise((resolve, reject) => {
    fetch('/api/data')
      .then(res => res.json())
      .then(data => resolve(data))
      .catch(err => reject(err));
  });
}

// ✅ 正确写法:fetch 本身就返回 Promise
function getData() {
  return fetch('/api/data').then(res => res.json());
}

七、Promise 与 async/await

async/await 是 ES2017 引入的语法糖,它让异步代码看起来完全像同步代码,是 Promise 的最佳实践。

基本用法

javascript 复制代码
// 用 async 标记函数
async function fetchData() {
  try {
    // 用 await 等待 Promise 完成
    const user = await getUserInfo(userId);
    const orders = await getOrderList(user.id);
    const goods = await getGoodsList(orders[0].id);
    console.log('最终获取到商品:', goods);
  } catch (err) {
    console.error('出错了:', err);
  }
}

并行执行多个异步任务

javascript 复制代码
async function fetchAllData() {
  // 并行执行三个请求
  const [user, orders, goods] = await Promise.all([
    getUserInfo(userId),
    getOrderList(userId),
    getGoodsList(goodsId)
  ]);
}

注意async/await 只是 Promise 的语法糖,它的底层依然是 Promise。


八、面试高频考点总结

  1. Promise 有哪几个状态?状态有什么特性? 三个状态:pending、fulfilled、rejected。状态一旦改变就不可逆。

  2. Promise 的 then 方法为什么能链式调用? 因为 then 方法会返回一个新的 Promise 实例。

  3. Promise.all、Promise.race、Promise.allSettled、Promise.any 的区别? 参考上面的对比表。

  4. Promise 的错误冒泡机制是什么? Promise 链中的错误会一直向后冒泡,直到被 catch 捕获。

  5. Promise 是同步还是异步? Promise 的执行器函数是同步执行的,thencatchfinally 回调是异步微任务。

  6. 手写一个符合 Promise/A+ 规范的 Promise。 参考上面的手写实现。

  7. async/await 和 Promise 的关系是什么? async/await 是 Promise 的语法糖,让异步代码看起来像同步代码。


写在最后

Promise 是现代 JavaScript 异步编程的基石,掌握它不仅能让你写出更优雅、更健壮的代码,更是面试中脱颖而出的必备技能。

希望这篇文章能帮你彻底搞懂 Promise,让你在实际开发和面试中都能游刃有余。

如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,有任何问题可以在评论区留言讨论!

相关推荐
桜吹雪4 小时前
所有智能体架构(1):反思 (Reflection)
javascript·人工智能
ZC跨境爬虫5 小时前
跟着 MDN 学 HTML day_61:(构建反馈表单的结构化挑战)
前端·javascript·ui·html·音视频
豹哥学前端6 小时前
JavaScript 异步编程完全指南:从回调地狱到 async/await,一次通关
前端·javascript·面试
kyriewen6 小时前
面试官让我手写Promise,我打开Cursor三秒生成,他愣了两秒说“你过了”
前端·javascript·面试
软件开发技术深度爱好者6 小时前
HTML实现DOCX文档版题库图文考试系统(修订)
前端·javascript·html
问征夫以前路6 小时前
Promise知识点回顾
前端·javascript
行走的陀螺仪6 小时前
JavaScript 算法详解:10大经典算法,通俗易懂,从入门到精通
开发语言·javascript·算法
yqcoder7 小时前
异步的魔法:深入解析 async/await 原理与编译本质
前端·javascript
taocarts_bidfans7 小时前
2026跨境SaaS工具选型指南:Taoify与Shopify/Shopyy/Ueeshop深度对比
java·前端·javascript·跨境电商·独立站