各位观众老爷,大家好!今天咱们来聊聊 JavaScript 里两位并发控制的“狠角色”:Promise.allSettled
和 Promise.any
(ES2021)。这俩哥们儿都是用来处理多个 Promise 的,但脾气秉性可不太一样。咱们这就来扒一扒他们的底细,看看在实际开发中怎么用好他们。
开场白:Promise 的并发困境
在异步编程的世界里,Promise 就像我们的快递小哥,辛辛苦苦地把结果送到我们手上。但如果我们要同时寄很多快递(发起多个 Promise),就得想办法有效地管理这些小哥。
传统的 Promise.all
就像一个严苛的监工,只要有一个快递小哥出了岔子(Promise rejected),整个任务就宣告失败,直接罢工。这在某些场景下显得过于死板,不够人性化。
而 Promise.race
则像一场赛跑,谁先送到就算谁赢,其他小哥直接被淘汰,这在需要所有结果的场景下就不适用了。
所以,我们需要更灵活的并发控制手段,这就是 Promise.allSettled
和 Promise.any
登场的原因。
第一位选手:Promise.allSettled
– 稳健先生
Promise.allSettled
就像一个老好人,无论 Promise 是 fulfilled 还是 rejected,他都会老老实实地把结果收集起来,一个都不落下。
语法:
Promise.allSettled([promise1, promise2, promise3, ...])
.then(results => {
// 处理所有 Promise 的结果
});
特性:
- 永远不会 rejected: 无论传入的 Promise 最终是 fulfilled 还是 rejected,
Promise.allSettled
自身都会 fulfilled,并且返回一个包含所有 Promise 结果的数组。 - 返回结果格式: 返回的数组中,每个元素都是一个对象,包含
status
和value
(fulfilled 时) 或reason
(rejected 时) 属性。
代码示例:
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error 2');
const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 100));
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
console.log(results);
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Error 2' },
{ status: 'fulfilled', value: 3 }
]
*/
// 区分 fulfilled 和 rejected,进行不同的处理
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Fulfilled:', result.value);
} else {
console.error('Rejected:', result.reason);
}
});
});
应用场景:
- 监控多个异步操作的状态: 当需要知道所有异步操作的成功与失败情况时,
Promise.allSettled
非常有用。例如,同时上传多个文件,无论成功与否,都需要通知用户上传结果。 - 容错处理: 在某些场景下,即使某些 Promise rejected,也希望继续执行后续操作,而不是直接中断。例如,从多个 API 获取数据,即使某些 API 请求失败,也希望展示其他 API 返回的数据。
- 并行执行,记录结果: 并行执行多个任务,并记录每个任务的执行结果,以便进行统计分析或错误排查。
表格总结:
特性 | 说明 |
---|---|
返回值 | 一个 Promise,fulfilled 后返回一个数组,数组中每个元素都是一个对象,包含 status (fulfilled 或 rejected) 和 value (fulfilled 时) 或 reason (rejected 时) 属性。 |
错误处理 | 永远不会 rejected,所有错误信息都会包含在返回结果中。 |
适用场景 | 需要知道所有 Promise 的成功与失败状态,并进行相应的处理。 |
优点 | 容错性强,不会因为某个 Promise rejected 而中断整个流程。 |
缺点 | 需要手动区分 fulfilled 和 rejected,代码相对繁琐。 |
第二位选手:Promise.any
– 乐观主义者
Promise.any
就像一个乐观主义者,只要有一个 Promise fulfilled,他就认为整个任务成功了,立刻返回第一个 fulfilled 的 Promise 的值。如果所有 Promise 都 rejected,他才会抛出一个 AggregateError
错误。
语法:
Promise.any([promise1, promise2, promise3, ...])
.then(value => {
// 处理第一个 fulfilled 的 Promise 的值
})
.catch(error => {
// 处理所有 Promise 都 rejected 的情况
});
特性:
- 返回第一个 fulfilled 的 Promise 的值: 只要有一个 Promise fulfilled,
Promise.any
就会立即 fulfilled,并返回该 Promise 的值。 - 如果所有 Promise 都 rejected,则 rejected: 如果所有 Promise 都 rejected,
Promise.any
就会 rejected,并抛出一个AggregateError
错误,其中包含所有 rejected 的原因。
代码示例:
const promise1 = Promise.reject('Error 1');
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve, reject) => setTimeout(() => reject('Error 3'), 100));
Promise.any([promise1, promise2, promise3])
.then(value => {
console.log('Fulfilled:', value); // 输出:Fulfilled: 2
})
.catch(error => {
console.error('All promises rejected:', error.errors);
/*
All promises rejected: [ 'Error 1', 'Error 3' ]
*/
});
const promise4 = Promise.reject('Error 4');
const promise5 = Promise.reject('Error 5');
Promise.any([promise4, promise5])
.then(value => {
console.log('Fulfilled:', value);
})
.catch(error => {
console.error('All promises rejected:', error.errors);
/*
All promises rejected: [ 'Error 4', 'Error 5' ]
*/
});
应用场景:
- 备选方案: 当有多个备选方案时,只要有一个方案成功,就可以认为整个任务成功。例如,从多个 CDN 加载资源,只要有一个 CDN 加载成功,就可以使用该资源。
- 服务降级: 当主服务不可用时,可以使用备用服务。
Promise.any
可以用来尝试使用多个服务,只要有一个服务可用,就可以继续提供服务。 - 延迟加载: 当需要加载多个资源时,可以并行加载这些资源,只要有一个资源加载完成,就可以先展示该资源,提高用户体验。
表格总结:
特性 | 说明 |
---|---|
返回值 | 一个 Promise,如果至少有一个 Promise fulfilled,则返回第一个 fulfilled 的 Promise 的值。如果所有 Promise 都 rejected,则 rejected,并抛出一个 AggregateError 错误,其中包含所有 rejected 的原因。 |
错误处理 | 如果所有 Promise 都 rejected,则 rejected,并抛出一个 AggregateError 错误。 |
适用场景 | 只需要一个 Promise fulfilled 即可,例如备选方案、服务降级等。 |
优点 | 可以快速返回结果,提高效率。 |
缺点 | 如果所有 Promise 都 rejected,则会抛出错误,需要进行错误处理。并且只有在所有 promise 都reject时才会抛出错误,如果部分reject,部分pending,则会一直等待直到有promise resolve或reject。如果所有的promise 都pending,那么Promise.any 也会一直pending |
对比总结:Promise.allSettled
vs Promise.any
特性 | Promise.allSettled |
Promise.any |
---|---|---|
返回值 | 一个 Promise,fulfilled 后返回一个数组,数组中每个元素都是一个对象,包含 status 和 value 或 reason 属性。 |
一个 Promise,如果至少有一个 Promise fulfilled,则返回第一个 fulfilled 的 Promise 的值。如果所有 Promise 都 rejected,则 rejected,并抛出一个 AggregateError 错误。 |
错误处理 | 永远不会 rejected,所有错误信息都会包含在返回结果中。 | 如果所有 Promise 都 rejected,则 rejected,并抛出一个 AggregateError 错误。 |
适用场景 | 需要知道所有 Promise 的成功与失败状态,并进行相应的处理。 | 只需要一个 Promise fulfilled 即可,例如备选方案、服务降级等。 |
优点 | 容错性强,不会因为某个 Promise rejected 而中断整个流程。 | 可以快速返回结果,提高效率。 |
缺点 | 需要手动区分 fulfilled 和 rejected,代码相对繁琐。 | 如果所有 Promise 都 rejected,则会抛出错误,需要进行错误处理。 |
适用场景例子 | 监控多个文件上传的状态,需要知道哪些成功,哪些失败。 | 尝试从多个 CDN 加载资源,只要有一个 CDN 加载成功即可。 |
实战演练:案例分析
案例一:用户注册 – 邮箱验证 (Promise.any)
用户注册时,我们通常会发送验证邮件。为了提高成功率,我们可以同时使用多个邮件服务提供商 (如 SendGrid, Mailgun, AWS SES)。只要有一个提供商发送成功,就认为验证邮件发送成功。
async function sendVerificationEmail(email, token) {
const sendGridPromise = sendEmailWithSendGrid(email, token);
const mailgunPromise = sendEmailWithMailgun(email, token);
const awsSesPromise = sendEmailWithAwsSes(email, token);
try {
await Promise.any([sendGridPromise, mailgunPromise, awsSesPromise]);
console.log('Verification email sent successfully!');
} catch (error) {
console.error('Failed to send verification email with any provider:', error.errors);
// 可以记录错误日志,并尝试其他方式通知用户
}
}
案例二:批量数据校验 (Promise.allSettled)
我们需要校验用户提交的批量数据,每个数据项的校验可能依赖于不同的 API。我们需要知道每个数据项的校验结果,以便向用户展示详细的错误信息。
async function validateBatchData(data) {
const validationPromises = data.map(item => validateDataItem(item)); //validateDataItem返回Promise
const results = await Promise.allSettled(validationPromises);
const errors = results.map((result, index) => {
if (result.status === 'rejected') {
return {
index: index,
error: result.reason,
};
}
return null;
}).filter(error => error !== null);
if (errors.length > 0) {
console.log('Validation errors:', errors);
// 向用户展示错误信息
} else {
console.log('All data items are valid!');
}
}
高级技巧:结合 async/await
使用 async/await
可以让代码更简洁易懂。
async function processPromises() {
const results = await Promise.allSettled([
Promise.resolve(1),
Promise.reject('Error'),
Promise.resolve(3),
]);
console.log(results);
try {
const value = await Promise.any([
Promise.reject('Error 1'),
Promise.resolve(2),
Promise.reject('Error 3'),
]);
console.log('Fulfilled:', value);
} catch (error) {
console.error('All promises rejected:', error.errors);
}
}
processPromises();
注意事项:
Promise.any
是 ES2021 引入的,需要确保运行环境支持。- 在使用
Promise.any
时,要 carefully 地处理AggregateError
,获取所有 rejected 的原因。 Promise.allSettled
相比Promise.all
,性能上可能会略有损失,因为需要处理所有 Promise 的结果。
总结:
Promise.allSettled
和 Promise.any
都是 Promise 并发控制的重要工具。Promise.allSettled
稳健可靠,适合需要知道所有 Promise 结果的场景。Promise.any
乐观高效,适合只需要一个 Promise 成功的场景。 根据不同的需求,选择合适的工具,才能写出更健壮、更高效的异步代码。
好了,今天的讲座就到这里。希望大家对 Promise.allSettled
和 Promise.any
有了更深入的了解。下次再见!