好的,各位听众,咱们今天来聊聊 JavaScript ES2021 里两个挺有意思的家伙:Promise.allSettled()
和 Promise.any()
。 这俩哥们儿虽然不像 Promise.all()
和 Promise.race()
那么频繁露脸,但在某些特定场景下,绝对能让你眼前一亮,大喊一声“真香!”。
开场白:Promise 的世界
在深入了解 Promise.allSettled()
和 Promise.any()
之前,咱们先简单回顾一下 Promise 的基本概念。Promise 简单来说,就是一个代表异步操作最终完成(或失败)的对象。它可以处于三种状态:
- pending (进行中): 初始状态,既没有成功,也没有失败。
- fulfilled (已成功): 操作成功完成。
- rejected (已失败): 操作失败。
Promise 解决了回调地狱的问题,让异步代码更易于管理和阅读。我们经常用到的 Promise.all()
和 Promise.race()
就像 Promise 世界里的明星,但今天我们要介绍的 Promise.allSettled()
和 Promise.any()
就像是默默耕耘的幕后英雄,虽然不常被提及,但关键时刻能发挥重要作用。
第一幕:Promise.allSettled() – 不抛弃,不放弃
设计意图
Promise.allSettled()
的设计意图非常明确:无论传入的 Promise 是成功还是失败,都要等到所有 Promise 都完成(settled)后,才返回一个包含所有 Promise 结果的数组。 换句话说,它不会像 Promise.all()
那样,只要有一个 Promise 失败就直接抛出错误,而是会收集所有 Promise 的结果,无论成功与否。
这种“不抛弃,不放弃”的精神,让 Promise.allSettled()
在处理需要收集所有结果的场景下非常有用,即使某些 Promise 失败了,我们仍然可以获取其他 Promise 的成功结果。
应用场景
想象一下这样一个场景:你需要同时请求多个 API 接口,然后根据所有接口的返回结果来更新 UI。但是,你并不希望因为其中一个接口请求失败就导致整个流程中断。这时,Promise.allSettled()
就派上用场了。
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
return Promise.reject(error); // 明确返回一个 rejected Promise
}
}
const urls = [
'https://jsonplaceholder.typicode.com/todos/1', // 肯定成功
'https://jsonplaceholder.typicode.com/todos/12345', // 肯定失败 (404)
'https://jsonplaceholder.typicode.com/posts/1', // 肯定成功
];
async function processData() {
const results = await Promise.allSettled(urls.map(url => fetchData(url)));
console.log(results); // 打印所有结果,包括成功和失败
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.error('失败:', result.reason);
}
});
}
processData();
在这个例子中,即使其中一个 URL 返回 404 错误,Promise.allSettled()
仍然会等待所有 Promise 完成,然后返回一个包含所有结果的数组。我们可以遍历这个数组,分别处理成功和失败的结果。
结果结构
Promise.allSettled()
返回的数组中的每个元素都是一个对象,该对象包含以下属性:
status
: 字符串,表示 Promise 的状态,可以是'fulfilled'
或'rejected'
。value
: 如果status
是'fulfilled'
,则value
属性包含 Promise 的成功结果。reason
: 如果status
是'rejected'
,则reason
属性包含 Promise 的失败原因。
用表格总结一下:
属性 | 类型 | 描述 |
---|---|---|
status |
string |
Promise 的状态,'fulfilled' 或 'rejected' |
value |
any |
Promise 成功的结果 (仅当 status 为 'fulfilled' ) |
reason |
any |
Promise 失败的原因 (仅当 status 为 'rejected' ) |
与 Promise.all() 的对比
Promise.all()
和 Promise.allSettled()
的主要区别在于错误处理:
特性 | Promise.all() |
Promise.allSettled() |
---|---|---|
错误处理 | 只要有一个 Promise 失败,就立即抛出错误,并中断执行 | 等待所有 Promise 完成,收集所有结果,包括成功和失败 |
返回值 | 成功时返回一个包含所有 Promise 结果的数组,失败时抛出错误 | 返回一个包含所有 Promise 结果的数组,每个结果包含 status 、value 或 reason |
适用场景 | 所有 Promise 都必须成功才能继续执行的场景 | 允许部分 Promise 失败,但仍需要收集所有结果的场景 |
第二幕:Promise.any() – 只要有一个成功,我就算你赢
设计意图
与 Promise.allSettled()
的“不抛弃,不放弃”相反,Promise.any()
的设计意图是:只要传入的 Promise 中有一个成功,就立即返回该成功 Promise 的结果。 只有当所有 Promise 都失败时,才会抛出一个 AggregateError
错误,其中包含所有 Promise 的失败原因。
Promise.any()
的这种“一将功成万骨枯”的精神,让它在处理需要多个备选方案,只要有一个方案成功就可以的场景下非常有用。
应用场景
假设你需要从多个 CDN 加载同一个资源,但你不知道哪个 CDN 的速度最快。你可以使用 Promise.any()
来同时请求所有 CDN,只要有一个 CDN 成功返回资源,就立即使用该资源,而无需等待其他 CDN。
async function fetchFromCDN(cdnUrl) {
try {
const response = await fetch(cdnUrl);
if (!response.ok) {
throw new Error(`CDN error! status: ${response.status}`);
}
return await response.text(); // 假设加载的是文本资源
} catch (error) {
return Promise.reject(error); // 明确返回一个 rejected Promise
}
}
const cdnUrls = [
'https://cdn1.example.com/resource.txt', // 假设这个 CDN 速度很快
'https://cdn2.example.com/resource.txt', // 假设这个 CDN 速度很慢
'https://cdn3.example.com/resource.txt', // 假设这个 CDN 可能挂了
];
async function loadResource() {
try {
const resource = await Promise.any(cdnUrls.map(url => fetchFromCDN(url)));
console.log('成功加载资源:', resource);
} catch (error) {
console.error('所有 CDN 都失败了:', error);
if (error instanceof AggregateError) {
console.log('失败原因:', error.errors); // 打印所有失败原因
}
}
}
loadResource();
在这个例子中,Promise.any()
会同时请求所有 CDN。只要其中一个 CDN 成功返回资源,Promise.any()
就会立即返回该资源,而无需等待其他 CDN。如果所有 CDN 都失败了,Promise.any()
会抛出一个 AggregateError
错误,其中包含所有 CDN 的失败原因。
AggregateError
当 Promise.any()
中所有 Promise 都失败时,会抛出一个 AggregateError
错误。AggregateError
是一个特殊的 Error 对象,它包含一个 errors
属性,该属性是一个数组,包含所有 Promise 的失败原因。
try {
await Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.reject('Error 3'),
]);
} catch (error) {
console.error(error); // AggregateError: All promises were rejected
console.log(error.errors); // ['Error 1', 'Error 2', 'Error 3']
}
与 Promise.race() 的对比
Promise.any()
和 Promise.race()
都是用于处理多个 Promise 的,但它们的行为有所不同:
特性 | Promise.race() |
Promise.any() |
---|---|---|
成功处理 | 只要有一个 Promise 完成(成功或失败),就立即返回该 Promise 的结果 | 只要有一个 Promise 成功,就立即返回该成功 Promise 的结果 |
失败处理 | 只要有一个 Promise 失败,就立即抛出错误 | 只有当所有 Promise 都失败时,才抛出一个 AggregateError 错误 |
适用场景 | 只需要最先完成的 Promise 的结果的场景 | 需要多个备选方案,只要有一个方案成功就可以的场景 |
第三幕:实战演练 – 图片上传优化
让我们来看一个更实际的例子:图片上传优化。假设你需要将一张图片上传到多个服务器,以提高上传的成功率和速度。你可以使用 Promise.any()
来实现这个功能。
async function uploadImage(serverUrl, imageFile) {
try {
const formData = new FormData();
formData.append('image', imageFile);
const response = await fetch(serverUrl, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed! status: ${response.status}`);
}
const data = await response.json();
return data.imageUrl; // 假设服务器返回图片 URL
} catch (error) {
return Promise.reject(error); // 明确返回一个 rejected Promise
}
}
async function uploadImageToMultipleServers(imageFile, serverUrls) {
try {
const imageUrl = await Promise.any(
serverUrls.map(url => uploadImage(url, imageFile))
);
console.log('图片上传成功,URL:', imageUrl);
return imageUrl;
} catch (error) {
console.error('所有服务器上传都失败了:', error);
// 处理所有服务器都失败的情况
return null;
}
}
// 示例
const imageFile = new File(['dummy data'], 'image.jpg', { type: 'image/jpeg' });
const serverUrls = [
'https://server1.example.com/upload',
'https://server2.example.com/upload',
'https://server3.example.com/upload',
];
uploadImageToMultipleServers(imageFile, serverUrls);
在这个例子中,uploadImageToMultipleServers()
函数会将图片上传到多个服务器。只要其中一个服务器上传成功,Promise.any()
就会立即返回该服务器返回的图片 URL。如果所有服务器都上传失败,Promise.any()
会抛出一个 AggregateError
错误,我们可以根据需要进行处理。
总结:选择合适的工具
Promise.allSettled()
和 Promise.any()
都是非常有用的工具,可以帮助我们更好地处理异步操作。选择哪个工具取决于你的具体需求:
- 如果你需要收集所有 Promise 的结果,无论成功与否,都应该使用
Promise.allSettled()
。 - 如果你只需要一个 Promise 成功就可以,并且不关心其他 Promise 的结果,就应该使用
Promise.any()
。
希望今天的讲座能帮助你更好地理解 Promise.allSettled()
和 Promise.any()
的设计意图和应用场景。记住,选择合适的工具,才能事半功倍!感谢大家的聆听!