一文彻底搞懂 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);
});
回调地狱的问题:
- 代码可读性差:层层嵌套,难以理解和维护
- 错误处理困难:每个回调都要单独处理错误
- 无法并行执行:多个异步任务只能串行执行,效率低下
- 无法返回值:异步任务的结果只能在回调内部使用
Promise 就是为了解决这些问题而生的。它将异步操作的结果和处理逻辑分离开,用链式调用的方式替代了嵌套回调,让异步代码变得像同步代码一样清晰易读。
二、Promise 核心定义与特性
1. 什么是 Promise?
Promise 是一个表示异步操作最终完成或失败的对象 。它本质上是一个状态机,用来管理异步操作的状态和结果。
你可以把 Promise 理解为一个承诺:
我承诺会在未来某个时间完成一件事,完成后我会通知你,你可以根据结果做相应的处理。
2. Promise 的三个状态
Promise 有且只有三个状态:
- pending(进行中):初始状态,异步操作正在执行
- fulfilled(已成功):异步操作成功完成
- rejected(已失败):异步操作失败
3. 最核心的特性:状态不可逆
Promise 的状态一旦从 pending 变为 fulfilled 或 rejected,就永远不会再改变。
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 的 then、catch、finally 回调都是微任务,会在当前宏任务执行完毕后、下一个宏任务开始前执行。
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。
八、面试高频考点总结
-
Promise 有哪几个状态?状态有什么特性? 三个状态:pending、fulfilled、rejected。状态一旦改变就不可逆。
-
Promise 的 then 方法为什么能链式调用? 因为
then方法会返回一个新的 Promise 实例。 -
Promise.all、Promise.race、Promise.allSettled、Promise.any 的区别? 参考上面的对比表。
-
Promise 的错误冒泡机制是什么? Promise 链中的错误会一直向后冒泡,直到被
catch捕获。 -
Promise 是同步还是异步? Promise 的执行器函数是同步执行的,
then、catch、finally回调是异步微任务。 -
手写一个符合 Promise/A+ 规范的 Promise。 参考上面的手写实现。
-
async/await 和 Promise 的关系是什么?
async/await是 Promise 的语法糖,让异步代码看起来像同步代码。
写在最后
Promise 是现代 JavaScript 异步编程的基石,掌握它不仅能让你写出更优雅、更健壮的代码,更是面试中脱颖而出的必备技能。
希望这篇文章能帮你彻底搞懂 Promise,让你在实际开发和面试中都能游刃有余。
如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,有任何问题可以在评论区留言讨论!