摘要:在现代前端开发中,异步编程是绕不开的话题。Promise.all 作为处理多个并發异步操作的利器,因其“快”而备受青睐——它能让所有任务同时发起,等待全部成功,从而大幅提升页面数据加载效率。
在现代前端开发中,异步编程是绕不开的话题。Promise.all 作为处理多个并發异步操作的利器,因其“快”而备受青睐——它能让所有任务同时发起,等待全部成功,从而大幅提升页面数据加载效率。
然而,这份“快”是有代价的,它背后隐藏着一个巨大的陷阱:脆弱性。换句话说,Promise.all 其实并不“安全”。
Promise.all 的工作机制遵循“全部或无一”的原则。它接收一个 Promise 数组,并返回一个新的 Promise。其行为是:
全部成功(Fulfilled):当所有输入的 Promise 都成功时,返回的 Promise 才会成功,结果是一个包含所有成功结果的数组。一个失败(Rejected):只要数组中任何一个 Promise 失败(rejected),返回的 Promise 就会立即失败,并抛出第一个失败的原因。这就是它最大的问题。让我们看一个例子:
// 假设有三个异步请求:获取用户信息、获取商品列表、获取消息通知const fetchUserInfo = fetch('/api/user');const fetchProducts = fetch('/api/products');const fetchNotifications = fetch('/api/notifications');Promise.all([fetchUserInfo, fetchProducts, fetchNotifications]) .then(([userInfo, products, notifications]) => { // 只有当三个请求都成功时,才会进入这里 renderUserPage(userInfo, products, notifications); }) .catch(error => { // 如果任何一个请求失败,就会跳到这里 console.error('有一个请求失败了:', error); showErrorPage('页面加载失败,请重试!'); });场景分析: 如果 fetchNotifications(获取通知)的接口挂了,返回了 500 错误,那么即使 fetchUserInfo 和 fetchProducts 已经成功请求回来了数据,Promise.all 也会立刻终止,直接跳入 .catch 分支。
结果就是:用户看到了一个全屏的错误提示,本已成功加载的用户信息和商品列表也无法展示给用户,体验极差。
为了解决 Promise.all 的这个痛点,ES2020 引入了 Promise.allSettled 方法。它的行为更加宽容和稳健:
等待所有:它会等待所有输入的 Promise 都“敲定”(settled),即无论是成功(fulfilled)还是失败(rejected)。永不失败:**Promise.allSettled 自身永远不会被 reject**,它总是会成功返回一个数组。详情可知:返回的数组中的每个对象都包含了每个 Promise 的最终状态和结果(或原因)。每个结果对象都有两种形态:
// 成功状态{ status: 'fulfilled', value: /* 成功的结果 */ }// 失败状态{ status: 'rejected', reason: /* 失败的原因(错误对象)*/ }我们用 Promise.allSettled 重构上面的例子:
Promise.allSettled([fetchUserInfo, fetchProducts, fetchNotifications]) .then((results) => { // 注意:这里永远不会 catch // results 是一个包含三个对象的数组 const userInfo = results[0].status === 'fulfilled' ? results[0].value : null; const products = results[1].status === 'fulfilled' ? results[1].value : null; const notifications = results[2].status === 'fulfilled' ? results[2].value : null; // 我们可以针对每个结果进行精细化处理 if (userInfo && products) { // 只要核心数据(用户和商品)还在,就渲染页面 renderUserPage(userInfo, products, notifications); // notifications 可能是 null if (!notifications) { showToast('通知获取失败,不影响主要功能'); } } else { // 如果核心数据缺失,再显示错误页 showErrorPage('核心数据加载失败'); } });// 不需要 .catch,因为它永远不会被触发而 Promise.allSettled 提供了一种更具弹性的并发处理模式,它允许我们接受部分失败,并基于完整的结果信息做出更优雅的降级处理,从而极大提升应用的健壮性和用户体验。
当下次你需要处理多个并发请求时,不妨先思考一下:这些任务之间的关系是强依赖还是弱依赖?如果答案是后者,那么 Promise.allSettled 就是你更安全、更现代的选择。
来源:不秃头程序员