一、先搞懂:Promise 是什么?
首先,Promise 是 JavaScript 中用于处理异步操作的一个内置对象(构造函数),它的核心作用是解决回调地狱问题,让异步代码的写法更优雅、更易维护。
可以用一个生活中的比喻来理解: 你去奶茶店点了一杯奶茶,店员给你一张取餐小票(Promise 对象),这张小票上包含了你的订单状态:
- 等待中(pending):奶茶还在制作,你不能马上拿到。
- 成功(fulfilled/resolved):奶茶制作完成,你可以取到奶茶(拿到异步操作的结果)。
- 失败(rejected):原料不足,奶茶做不了了(拿到异步操作的错误信息)。
这三种状态就是 Promise 的三种核心状态,且有一个重要特性:状态一旦改变(pending → fulfilled 或 pending → rejected),就再也无法修改(不可逆),就像奶茶店小票一旦显示“制作完成”,就不可能再变回“制作中”。
另外,Promise 还有一个值(value/reason):
- 状态变为 fulfilled 时,会有一个成功的值(比如异步请求返回的数据)。
- 状态变为 rejected 时,会有一个失败的原因(比如错误信息)。
二、为什么需要 Promise?
在 Promise 出现之前,JavaScript 处理异步操作(如 AJAX 请求、定时器)只能用回调函数,当遇到多个嵌套的异步操作时,就会出现回调地狱(Callback Hell)。
看一个回调地狱的例子(多层定时器嵌套):
// 需求:1秒后输出A,再过2秒输出B,再过3秒输出C
setTimeout(() => {
console.log("A");
setTimeout(() => {
console.log("B");
setTimeout(() => {
console.log("C");
}, 3000);
}, 2000);
}, 1000);
这段代码的问题很明显:
- 代码层层嵌套,像“金字塔”一样,可读性极差。
- 后期维护困难,若要修改中间步骤,需要逐层拆解。
- 错误处理繁琐,每层回调都需要单独处理错误。
而 Promise 就是为了解决这些问题而生的,它可以将嵌套的回调改为链式调用,让代码更扁平、更易管理。
三、Promise 的核心用法
1. 基本语法:创建 Promise 实例
Promise 是一个构造函数,创建实例时需要传入一个执行器函数(executor),这个执行器函数会立即执行,且接收两个内置参数:resolve(成功回调)和 reject(失败回调)。
// 创建 Promise 实例
const myPromise = new Promise((resolve, reject) => {
// 这里写异步操作逻辑(也可以写同步逻辑,只是Promise通常用于异步)
const isSuccess = true; // 模拟异步操作是否成功
setTimeout(() => {
if (isSuccess) {
// 异步操作成功:调用 resolve,将 Promise 状态改为 fulfilled
// 可以传递一个成功的值(任意类型)
resolve("异步操作执行成功!");
} else {
// 异步操作失败:调用 reject,将 Promise 状态改为 rejected
// 可以传递一个失败的原因(任意类型,通常是 Error 对象)
reject(new Error("异步操作执行失败!"));
}
}, 1000);
});
注意点:
- 执行器函数会立即执行,不需要等待后续调用。
resolve和reject只能调用其中一个(因为状态不可逆,调用第二个会无效)。- 建议
reject传递Error对象,便于后续捕获错误详情。
2. 消费 Promise 实例:获取异步结果
创建 Promise 实例后,需要通过实例方法来消费它,获取成功或失败的结果,核心方法有两个:.then() 和 .catch()。
(1).then() 方法:处理成功状态
.then() 接收两个可选参数(都是函数):
- 第一个参数:Promise 状态为
fulfilled时的回调(处理成功结果)。 - 第二个参数:Promise 状态为
rejected时的回调(处理失败结果,可选,通常推荐用.catch()单独处理失败)。
// 消费 Promise 实例
myPromise.then(
// 第一个参数:处理成功结果
(successValue) => {
console.log("成功:", successValue); // 输出:成功: 异步操作执行成功!
},
// 第二个参数:处理失败结果(可选)
(errorReason) => {
console.log("失败:", errorReason);
}
);
(2).catch() 方法:处理失败状态
.catch() 专门用于处理 Promise 的 rejected 状态,以及 .then() 回调中抛出的错误,相当于 .then(null, onRejected) 的语法糖,更易读、更常用。
// 推荐写法:.then() 处理成功,.catch() 处理失败
myPromise
.then((successValue) => {
console.log("成功:", successValue);
})
.catch((errorReason) => {
console.log("失败:", errorReason.message);
});
修改 isSuccess = false,运行结果:失败: 异步操作执行失败!
(3).finally() 方法:无论成功失败都会执行
.finally() 是一个可选的实例方法,它的回调函数不接收任何参数,且无论 Promise 状态是 fulfilled 还是 rejected,都会在 .then() 或 .catch() 执行完毕后执行,通常用于做清理工作(如关闭加载动画、释放资源)。
myPromise
.then((successValue) => {
console.log("成功:", successValue);
})
.catch((errorReason) => {
console.log("失败:", errorReason.message);
})
.finally(() => {
console.log("异步操作结束,无论成功失败都会执行");
});
3. 解决回调地狱:Promise 链式调用
.then() 方法有一个重要特性:返回值是一个新的 Promise 实例,这就意味着我们可以进行链式调用,将多层嵌套的异步操作改为扁平的链式写法,彻底解决回调地狱。
需求:用 Promise 实现“1秒后输出A,再过2秒输出B,再过3秒输出C”,避免回调地狱:
// 封装一个返回 Promise 的定时器函数
function delayLog(content, delayTime) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(content);
resolve(); // 执行完当前异步操作,调用 resolve 触发下一个 .then()
}, delayTime);
});
}
// 链式调用:扁平结构,无嵌套
delayLog("A", 1000)
.then(() => delayLog("B", 2000))
.then(() => delayLog("C", 3000));
这段代码的执行流程:
- 执行
delayLog("A", 1000),返回一个 Promise 实例。 - 1秒后输出 A,调用
resolve(),触发第一个.then()。 - 第一个
.then()中返回delayLog("B", 2000)(新的 Promise 实例)。 - 再过2秒输出 B,调用
resolve(),触发第二个.then()。 - 第二个
.then()中返回delayLog("C", 3000)(新的 Promise 实例)。 - 再过3秒输出 C,执行完毕。
可以看到,链式调用让异步代码的执行流程更清晰,就像同步代码一样从上到下执行,极大提升了可读性和可维护性。
四、补充:Promise 的常见误区
- 忘记返回 Promise:链式调用时,如果
.then()中没有返回 Promise,下一个.then()会立即执行,无法实现异步操作的顺序执行。 - 错误捕获不全面:只捕获 Promise 本身的失败,忽略了
.then()回调中抛出的同步错误(.catch()可以捕获这类错误,推荐优先使用.catch())。 - 认为
Promise可以完全替代回调函数:Promise 本质上还是对回调函数的封装,只是让回调更优雅,并非完全抛弃回调。
六、总结
- Promise 是 JS 用于处理异步操作的内置对象,核心解决回调地狱问题,状态不可逆(pending → fulfilled / rejected)。
- 核心用法:
new Promise()创建实例(传入执行器函数),.then()处理成功、.catch()处理失败、.finally()做收尾工作,支持链式调用实现异步操作顺序执行。 - 常用静态方法:
Promise.resolve()/Promise.reject()快速创建 Promise,Promise.all()等待所有异步操作成功,Promise.race()取最快完成的异步操作结果。 - Promise 让异步代码更扁平、更易维护,是后续
async/await(异步编程终极方案)的基础,掌握 Promise 是学好 JS 异步编程的关键。