Promise.allSettled:当“完美”不可期,我们该如何优雅谢幕?
在人生的舞台上,我们总会遇到各种各样的承诺,大到“我要改变世界”,小到“我明天准时还你钱”。这些承诺就像一个个Promise,承载着我们的期望,也可能带来失望。在JavaScript的世界里,Promise也是同样的角色,它们代表着异步操作的最终结果,成功了皆大欢喜,失败了也得有个说法。
想象一下,你是一个乐队的经纪人,雄心勃勃地安排了一场全国巡演。你给每个城市的演出场馆都发出了预定请求,每一个请求都像一个Promise,代表着一个城市演出的成功与否。如果一切顺利,你的巡演计划就能完美执行,乐队也能名利双收。
但是,人生不如意十之八九。也许某个城市因为不可抗力取消了演出,也许某个场馆临时出了故障。如果其中一个Promise失败了,你难道要取消整个巡演吗?乐队成员怕是要跟你拼命!
这个时候,Promise.allSettled
就派上用场了。它就像一个经验丰富的巡演总监,即使有些城市出了问题,也能确保整个巡演继续进行,并且还能给你一份详细的报告,告诉你哪些城市成功了,哪些城市失败了,以及失败的原因。
什么是Promise.allSettled?
简单来说,Promise.allSettled
是一个静态方法,它接收一个Promise数组作为参数,并返回一个新的Promise。这个新的Promise会在所有输入的Promise都完成(无论成功还是失败)后resolve。
与Promise.all
不同的是,Promise.allSettled
不会因为其中一个Promise的失败而立即reject。它会等待所有Promise都完成,然后返回一个包含每个Promise结果的数组。
Promise.allSettled的返回值
Promise.allSettled
返回的数组中,每个元素都是一个对象,包含以下两个属性:
status
: 字符串,表示Promise的结果状态,可以是"fulfilled"
(成功) 或"rejected"
(失败)。value
: 如果Promise成功,则包含Promise的resolve值。reason
: 如果Promise失败,则包含Promise的reject原因。
一个简单的例子
让我们回到乐队巡演的例子。假设我们有三个Promise,分别代表三个城市的演出预定:
const city1 = Promise.resolve("北京演出预定成功!");
const city2 = Promise.reject("上海演出场馆故障,预定失败!");
const city3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("广州演出预定成功!");
}, 1000); // 模拟异步操作
});
Promise.allSettled([city1, city2, city3])
.then((results) => {
console.log(results);
// 输出结果:
// [
// { status: 'fulfilled', value: '北京演出预定成功!' },
// { status: 'rejected', reason: '上海演出场馆故障,预定失败!' },
// { status: 'fulfilled', value: '广州演出预定成功!' }
// ]
// 可以根据results分析巡演情况,制定应对策略
let successfulCities = results.filter(result => result.status === 'fulfilled').map(result => result.value);
let failedCities = results.filter(result => result.status === 'rejected').map(result => result.reason);
console.log("成功预定的城市:", successfulCities); // 输出:成功预定的城市: [ '北京演出预定成功!', '广州演出预定成功!' ]
console.log("预定失败的城市:", failedCities); // 输出:预定失败的城市: [ '上海演出场馆故障,预定失败!' ]
});
在这个例子中,即使city2
的Promise失败了,Promise.allSettled
仍然等待所有Promise完成,并返回一个包含所有结果的数组。我们可以通过分析这个数组,了解每个城市的预定情况,并据此制定相应的策略。比如,我们可以联系上海的其他场馆,或者调整巡演计划,将上海的演出安排到其他时间。
Promise.allSettled的应用场景
Promise.allSettled
在以下场景中非常有用:
- 需要知道所有Promise的结果,即使其中一些Promise失败了。 比如,在发送多个请求时,我们可能需要知道哪些请求成功了,哪些请求失败了,以便进行错误处理和重试。
- 需要并行执行多个独立的异步操作,并且不希望其中一个操作的失败影响其他操作。 比如,在加载多个资源时,我们不希望因为一个资源加载失败而阻止其他资源的加载。
- 需要对多个Promise的结果进行统一处理。 比如,在处理用户提交的多个表单时,我们可以使用
Promise.allSettled
来等待所有表单验证完成,然后根据验证结果进行相应的操作。
Promise.allSettled vs Promise.all vs Promise.race vs Promise.any
JavaScript中还有其他几个处理Promise数组的方法,它们分别是Promise.all
、Promise.race
和Promise.any
。让我们来对比一下它们之间的区别:
Promise.all
: 如果所有Promise都成功,则resolve一个包含所有resolve值的数组。如果其中任何一个Promise失败,则立即reject,并返回第一个reject原因。就像追求完美主义的艺术家,任何瑕疵都无法容忍。Promise.race
: 返回第一个resolve或reject的Promise的结果。就像赛跑一样,谁先到达终点,就以谁的结果为准。Promise.any
: 返回第一个resolve的Promise的结果。如果所有Promise都reject,则reject一个AggregateError
,包含所有reject原因。就像大海捞针,只要找到一根,就算成功。
方法 | 成功条件 | 失败条件 | 返回值 |
---|---|---|---|
Promise.all |
所有Promise都resolve | 任何一个Promise reject | 包含所有resolve值的数组 |
Promise.allSettled |
所有Promise都完成(无论resolve或reject) | 无 | 包含所有Promise结果的数组,每个结果包含status 、value 或reason 属性 |
Promise.race |
任何一个Promise resolve或reject | 无 | 第一个resolve或reject的Promise的结果 |
Promise.any |
任何一个Promise resolve | 所有Promise都reject | 第一个resolve的Promise的结果。如果所有Promise都reject,则reject一个AggregateError ,包含所有reject原因 |
选择哪个方法取决于你的具体需求。如果你需要确保所有Promise都成功,并且希望在任何一个Promise失败时立即停止,那么Promise.all
是最好的选择。如果你只需要知道第一个完成的Promise的结果,那么Promise.race
是最好的选择。如果你只需要知道是否有任何一个Promise成功,那么Promise.any
是最好的选择。而如果你需要知道所有Promise的结果,无论成功还是失败,那么Promise.allSettled
就是你的不二之选。
Promise.allSettled的兼容性
Promise.allSettled
是ES2020引入的新特性。这意味着一些旧版本的浏览器可能不支持它。如果你需要在旧版本的浏览器中使用Promise.allSettled
,可以使用polyfill。
一个更复杂的例子:用户注册流程
让我们来看一个更复杂的例子,模拟一个用户注册流程。在这个流程中,我们需要执行以下几个步骤:
- 验证用户名是否可用。
- 验证邮箱是否可用。
- 创建用户账户。
- 发送欢迎邮件。
每个步骤都是一个异步操作,并且可能会失败。我们希望在所有步骤都完成后,向用户显示注册结果,无论成功还是失败。
async function registerUser(username, email, password) {
const validateUsername = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username === "admin") {
reject("用户名已被占用!");
} else {
resolve("用户名可用。");
}
}, 500); // 模拟网络延迟
});
};
const validateEmail = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (email === "[email protected]") {
reject("邮箱已被注册!");
} else {
resolve("邮箱可用。");
}
}, 800); // 模拟网络延迟
});
};
const createUserAccount = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 假设创建账户失败
// reject("创建账户失败!");
resolve("账户创建成功!");
}, 1200); // 模拟网络延迟
});
};
const sendWelcomeEmail = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 假设发送邮件失败
// reject("发送欢迎邮件失败!");
resolve("欢迎邮件发送成功!");
}, 1500); // 模拟网络延迟
});
};
const promises = [
validateUsername(),
validateEmail(),
createUserAccount(),
sendWelcomeEmail(),
];
const results = await Promise.allSettled(promises);
console.log("注册结果:", results);
// 根据结果显示注册状态
let successCount = 0;
let failureMessages = [];
results.forEach((result) => {
if (result.status === "fulfilled") {
successCount++;
console.log(result.value); // 打印成功信息
} else {
failureMessages.push(result.reason);
console.error(result.reason); // 打印错误信息
}
});
if (failureMessages.length === 0) {
console.log("恭喜你,注册成功!");
} else {
console.log("注册失败,请检查以下错误:");
failureMessages.forEach((message) => console.log("- " + message));
}
}
registerUser("newUser", "[email protected]", "password123");
在这个例子中,我们使用Promise.allSettled
来等待所有注册步骤完成。然后,我们根据结果显示注册状态,并向用户提供详细的错误信息。即使某些步骤失败了,用户仍然可以知道哪些步骤成功了,哪些步骤失败了,以及失败的原因。这可以帮助用户更快地解决问题,并提高用户体验。
总结
Promise.allSettled
是一个强大的工具,可以帮助我们处理复杂的异步操作,即使其中一些操作失败了,也能保证程序的稳定性和可靠性。它就像一个成熟稳重的朋友,即使在你遇到挫折时,也能给你支持和鼓励,并帮助你找到前进的方向。
希望这篇文章能帮助你更好地理解Promise.allSettled
的用法和应用场景。下次当你需要处理多个Promise时,不妨试试Promise.allSettled
,它可能会给你带来意想不到的惊喜!记住,人生就像一场巡演,总会有一些不如意,但只要我们坚持下去,总会找到属于自己的舞台。