各位观众,晚上好!今儿咱们聊聊 JavaScript 里头一个挺实用,但有时候又容易被忽略的家伙:Promise.allSettled
。这玩意儿啊,能让你在处理一堆 Promise 的时候,甭管它们是成功还是失败,都能安安心心地把结果都拿到手。不像 Promise.all
那样,只要有一个 Promise 崩了,整个就歇菜了。
啥是 Promise.allSettled
?
简单来说,Promise.allSettled
接收一个 Promise 数组(或者任何可迭代的 Promise ),然后它会等待数组里的所有 Promise 都完成(resolved 或 rejected)。 无论每个 Promise 的结果如何,Promise.allSettled
都会返回一个包含所有 Promise 结果的数组。
这个结果数组的每个元素都是一个对象,包含两个属性:
status
: 字符串,表示 Promise 的状态,可能是"fulfilled"
(成功) 或"rejected"
(失败)。value
: 如果status
是"fulfilled"
,则包含 Promise 的 resolved 值。reason
: 如果status
是"rejected"
,则包含 Promise 的 rejected 原因。
为什么需要 Promise.allSettled
?
想象一下,你在做一个批量处理的任务,需要调用多个 API 接口。如果其中一个接口挂了,你肯定不想整个任务都失败吧?Promise.allSettled
就能帮你搞定这个场景。它可以让你知道哪些接口成功了,哪些接口失败了,然后你可以针对失败的接口进行重试或者其他处理。
再比如,你要同时加载多个资源,但有些资源可能加载失败。使用 Promise.allSettled
,你可以加载所有能加载的资源,然后忽略加载失败的资源,而不是直接抛出错误导致整个页面崩溃。
Promise.allSettled
的基本用法
咱们先来看一个简单的例子:
const promise1 = Promise.resolve(123);
const promise2 = Promise.reject("出错了!");
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 2000, "成功了!"));
Promise.allSettled([promise1, promise2, promise3])
.then((results) => {
console.log(results);
});
这段代码的输出大概是这样的(注意,promise3
的完成时间会影响最终输出的顺序):
[
{ "status": "fulfilled", "value": 123 },
{ "status": "rejected", "reason": "出错了!" },
{ "status": "fulfilled", "value": "成功了!" }
]
可以看到,Promise.allSettled
返回的数组包含了每个 Promise 的状态和结果。 即使 promise2
失败了,整个 Promise.allSettled
仍然成功完成,并且返回了 promise2
的 rejected 原因。
Promise.allSettled
和 Promise.all
的区别
特性 | Promise.all |
Promise.allSettled |
---|---|---|
失败处理 | 只要有一个 Promise 失败,整个 Promise 就立即失败。 | 等待所有 Promise 完成,即使有 Promise 失败。 |
返回值 | 返回一个 Promise,resolved 值为所有 Promise 的结果数组。 | 返回一个 Promise,resolved 值为包含每个 Promise 状态和结果的数组。 |
适用场景 | 所有 Promise 都必须成功才能继续的场景。 | 需要处理部分 Promise 失败的情况,并且仍然需要知道所有 Promise 的结果的场景。 |
简单来说,Promise.all
就像一个团队,只要有一个人掉链子,整个团队就完蛋。而 Promise.allSettled
就像一个探险队,即使有人受伤了,探险队仍然会完成任务,并且记录下每个人的情况。
实际应用场景
1. 并行请求多个 API
async function fetchData(urls) {
const promises = urls.map(url =>
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error(`Error fetching ${url}: ${error}`);
return null; // 返回 null 或其他默认值,避免 Promise.all 提前终止
})
);
const results = await Promise.allSettled(promises);
const successfulData = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failedUrls = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
console.log("成功获取的数据:", successfulData);
console.log("失败的 URLs:", failedUrls);
return { successfulData, failedUrls };
}
const urls = [
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/invalid-url"
];
fetchData(urls);
在这个例子中,我们并行请求了三个 API 接口。即使其中一个接口 (https://jsonplaceholder.typicode.com/invalid-url
) 返回了错误,Promise.allSettled
仍然会等待其他接口完成,并且返回所有接口的结果。 这样我们就可以知道哪些接口成功了,哪些接口失败了,然后进行相应的处理。
2. 加载多个资源
async function loadImages(imageUrls) {
const promises = imageUrls.map(url =>
new Promise(resolve => {
const img = new Image();
img.onload = () => {
console.log(`Image loaded: ${url}`);
resolve({ url: url, element: img });
};
img.onerror = () => {
console.error(`Failed to load image: ${url}`);
resolve({ url: url, error: new Error(`Failed to load image: ${url}`) }); // 这里resolve,而不是reject
};
img.src = url;
})
);
const results = await Promise.allSettled(promises);
const loadedImages = results
.filter(result => result.status === 'fulfilled' && !result.value.error)
.map(result => result.value.element);
const failedImageUrls = results
.filter(result => result.status === 'fulfilled' && result.value.error)
.map(result => result.value.url);
console.log("成功加载的图片:", loadedImages);
console.log("加载失败的图片 URLs:", failedImageUrls);
return { loadedImages, failedImageUrls };
}
const imageUrls = [
"https://via.placeholder.com/150",
"https://via.placeholder.com/200",
"https://invalid-image-url.com/image.jpg"
];
loadImages(imageUrls);
在这个例子中,我们尝试加载多个图片。即使其中一个图片加载失败,Promise.allSettled
仍然会等待其他图片加载完成,并且返回所有图片的结果。 这样我们就可以显示所有成功加载的图片,并且忽略加载失败的图片,而不是直接抛出错误导致整个页面崩溃。注意这里onerror
中要resolve
而不是reject
,因为Promise.allSettled
需要所有promise都settled,所以要通过resolve
来告知promise已经完成。
3. 表单验证
async function validateForm(formData) {
const validationPromises = [
validateUsername(formData.username),
validateEmail(formData.email),
validatePassword(formData.password)
];
const results = await Promise.allSettled(validationPromises);
const errors = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
if (errors.length > 0) {
console.error("表单验证失败:", errors);
return { isValid: false, errors: errors };
} else {
console.log("表单验证成功!");
return { isValid: true };
}
}
async function validateUsername(username) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username.length < 5) {
reject("用户名长度必须大于等于 5 个字符");
} else {
resolve();
}
}, 500);
});
}
async function validateEmail(email) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!email.includes('@')) {
reject("邮箱格式不正确");
} else {
resolve();
}
}, 300);
});
}
async function validatePassword(password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (password.length < 8) {
reject("密码长度必须大于等于 8 个字符");
} else {
resolve();
}
}, 700);
});
}
const formData = {
username: "user",
email: "invalid-email",
password: "short"
};
validateForm(formData);
在这个例子中,我们并行验证表单中的用户名、邮箱和密码。即使其中一个验证失败,Promise.allSettled
仍然会等待其他验证完成,并且返回所有验证的结果。 这样我们就可以知道哪些字段验证失败了,并且将错误信息显示给用户。
Promise.allSettled
的一些注意事项
- 错误处理:
Promise.allSettled
不会抛出任何错误。 你需要自己检查返回结果数组中的每个 Promise 的状态,并且根据状态进行相应的处理。 - 顺序:
Promise.allSettled
返回的结果数组的顺序与传入的 Promise 数组的顺序相同。 - 性能:
Promise.allSettled
会等待所有 Promise 都完成,所以如果其中一个 Promise 花费很长时间才能完成,那么整个Promise.allSettled
也会花费很长时间。
Promise.allSettled
和 Promise.any
的对比 (ES2021)
Promise.any
和 Promise.allSettled
都是 ES2021 引入的,它们都用于处理多个 Promise。 但它们的行为完全不同。
Promise.any
: 只要有一个 Promise 成功,就立即返回该 Promise 的 resolved 值。 如果所有 Promise 都失败了,则抛出一个AggregateError
错误。Promise.allSettled
: 等待所有 Promise 完成,无论成功或失败,然后返回一个包含每个 Promise 状态和结果的数组。
特性 | Promise.any |
Promise.allSettled |
---|---|---|
成功条件 | 只要有一个 Promise 成功,就立即成功。 | 必须等待所有 Promise 完成。 |
失败条件 | 所有 Promise 都失败,才失败。 | 永远不会失败,总是返回一个 resolved 的 Promise。 |
返回值 | 返回第一个成功的 Promise 的 resolved 值。 | 返回一个包含每个 Promise 状态和结果的数组。 |
错误处理 | 如果所有 Promise 都失败,则抛出一个 AggregateError 错误。 |
需要自己检查返回结果数组中的每个 Promise 的状态,并且根据状态进行相应的处理。 |
适用场景 | 只需要一个 Promise 成功即可的场景,例如:尝试多个服务器,只要有一个服务器可用即可。 | 需要知道所有 Promise 的结果的场景,无论成功或失败。 |
手动实现 Promise.allSettled
(Polyfill)
如果你的运行环境不支持 Promise.allSettled
,你可以自己实现一个 polyfill:
if (!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(
Array.from(promises).map(promise => {
return Promise.resolve(promise)
.then(
value => ({ status: 'fulfilled', value: value }),
reason => ({ status: 'rejected', reason: reason })
);
})
);
};
}
这个 polyfill 的原理很简单:
- 将传入的 Promise 数组转换为一个数组。
- 使用
map
方法遍历数组中的每个 Promise。 - 对于每个 Promise,使用
Promise.resolve
将其转换为一个 Promise 对象(如果它还不是)。 - 使用
then
方法处理 Promise 的成功和失败情况。 - 如果 Promise 成功,则返回一个包含
status
为"fulfilled"
和value
为 Promise 的 resolved 值的对象。 - 如果 Promise 失败,则返回一个包含
status
为"rejected"
和reason
为 Promise 的 rejected 原因的对象。 - 使用
Promise.all
等待所有 Promise 完成,并且返回包含每个 Promise 状态和结果的数组。
总结
Promise.allSettled
是一个非常有用的工具,可以让你在处理多个 Promise 时更加灵活和可靠。 它可以让你知道哪些 Promise 成功了,哪些 Promise 失败了,然后你可以针对失败的 Promise 进行重试或者其他处理。 记住,它和 Promise.all
的行为完全不同,不要混淆使用。
希望今天的讲座对大家有所帮助! 咱们下次再见!