1. 什么是 Promise?
Promise 是 JavaScript 中处理异步操作的一种模式。它是一个对象,代表了一个尚未完成但预期将来会完成的操作的结果。简单来说,它像一个承诺:现在我可能无法立即给你结果,但我承诺将来会给你一个结果(要么成功,要么失败)。
主要解决的问题:
- 回调地狱 (Callback Hell): 避免多层嵌套的回调函数,使代码更易读、更易维护。
- 异步流程控制: 提供更清晰、更强大的异步操作管理方式。
2. Promise 的三种状态
一个 Promise 实例必然处于以下三种状态之一:
- Pending (进行中): 初始状态,异步操作正在进行中,尚未完成(既未成功也未失败)。
- Fulfilled (已成功): 异步操作成功完成。此时 Promise 有一个最终的值 (value)。
- Rejected (已失败): 异步操作失败。此时 Promise 有一个原因 (reason),通常是一个 Error 对象。
状态转换:
- Promise 的状态只能从
Pending变为Fulfilled或Rejected。 - 一旦状态改变(变为
Fulfilled或Rejected),就不能再改变了。这个过程称为 "settled"(已敲定)。
3. 创建 Promise
我们使用 new Promise() 构造函数来创建一个 Promise 实例。构造函数接收一个函数作为参数,这个函数被称为 "executor"。
Executor 函数会立即执行,并接收两个参数:resolve 和 reject。这两个参数本身也是函数。
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还是Rejected,onFinally函数都会被执行。 - 它不接收任何参数,通常用于执行一些清理工作(例如隐藏加载指示器)。
- 它也返回一个新的 Promise。
- 无论 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来捕获)。
- 如果 Promise 成功 (Fulfilled),
示例 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 链实现了相同的功能,但代码结构看起来更像传统的同步代码。