各位观众老爷们,大家好!我是你们的老朋友,一个在代码堆里摸爬滚打多年的“老码农”。今天咱们不聊那些虚头巴脑的框架和设计模式,就来聊聊JavaScript里两个“狠角色”:Promise.allSettled()
和 Promise.any()
。 这俩兄弟,在并发任务管理方面可是相当给力,能让你的异步代码更加健壮,更优雅。
咱们今天要讲的主题是:并发任务管理中的Promise.allSettled() 和 Promise.any() 的具体应用场景。
先说好,今天咱们的目标是:用最通俗的语言,最实用的例子,把这俩哥们的用法和适用场景给彻底搞明白。 保证各位听完之后,下次再碰到类似问题,能立马想到用它们来“收拾”!
一、 Promise.allSettled() : “一个都不能少” 的全面汇报
Promise.allSettled()
就像一个尽职尽责的HR,负责收集所有员工的绩效报告,无论成功还是失败,都要汇总汇报。它接收一个Promise数组,并返回一个新的Promise。这个新的Promise会在所有输入的Promise都完成(fulfilled or rejected)后resolve, resolve的结果是一个包含所有Promise结果的数组。
1. 语法结构:
Promise.allSettled(iterable);
iterable
: 一个可迭代对象,例如 Array。
2. 返回值:
一个 Promise
,resolve时返回一个数组,数组中的每个元素都是一个对象,包含以下属性:
status
: 表示Promise的状态,可以是"fulfilled"
(成功) 或"rejected"
(失败)。value
: 如果status
是"fulfilled"
,则包含Promise的resolve值。reason
: 如果status
是"rejected"
,则包含Promise的reject原因。
3. 应用场景:
-
监控多个异步操作的完成状态,即使其中一些操作失败,也要保证所有操作都执行完毕。
想象一下,你要同时向多个服务器发送请求,记录每个请求的结果,即使某个服务器挂了,也不能影响其他服务器的请求结果记录。 这时候,
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) { console.error(`请求 ${url} 失败:`, error); throw error; // 重新抛出错误,让 allSettled 记录 } } const urls = [ 'https://rickandmortyapi.com/api/character', // 确保这个 URL 可用 'https://rickandmortyapi.com/api/location', // 确保这个 URL 可用 'https://rickandmortyapi.com/api/episode', // 确保这个 URL 可用 'https://some-nonexistent-api.com/data' // 故意制造一个失败的请求 ]; async function processData() { const promises = urls.map(url => fetchData(url)); const results = await Promise.allSettled(promises); results.forEach((result, index) => { console.log(`请求 ${urls[index]} 的结果:`); if (result.status === 'fulfilled') { console.log('状态: 成功'); console.log('数据:', result.value); } else { console.log('状态: 失败'); console.log('原因:', result.reason); } console.log('---'); }); } processData();
在这个例子中,即使
https://some-nonexistent-api.com/data
这个URL请求失败,Promise.allSettled()
仍然会等待所有请求完成,并返回一个包含所有请求结果的数组,你可以清晰地看到哪些请求成功了,哪些请求失败了,以及失败的原因。 -
批量更新UI,并显示每个操作的结果,无论成功与否。
假设你正在开发一个图片上传功能,用户可以选择多个图片同时上传。 你需要显示每个图片的上传状态,成功或失败。
async function uploadImage(file) { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟上传过程 const random = Math.random(); if (random > 0.5) { console.log(`上传成功: ${file.name}`); resolve({ success: true, filename: file.name }); } else { console.error(`上传失败: ${file.name}`); reject({ success: false, filename: file.name, error: '上传失败' }); } }, 500); // 模拟上传延迟 }); } async function processUploads(files) { const promises = Array.from(files).map(file => uploadImage(file)); const results = await Promise.allSettled(promises); results.forEach(result => { const filename = result.value?.filename || result.reason?.filename; const statusElement = document.getElementById(`${filename}-status`); // 假设有对应的 UI 元素 if (result.status === 'fulfilled') { statusElement.textContent = `${filename} 上传成功!`; } else { statusElement.textContent = `${filename} 上传失败:${result.reason.error}`; } }); } // 模拟用户选择文件 const files = [ new File([''], 'image1.jpg', { type: 'image/jpeg' }), new File([''], 'image2.png', { type: 'image/png' }), new File([''], 'image3.gif', { type: 'image/gif' }) ]; // 模拟触发上传 processUploads(files); // (为了让这段代码能运行,你需要创建一个简单的 HTML 页面,包含一些 id 为 "image1.jpg-status", "image2.png-status", "image3.gif-status" 的元素)
这段代码模拟了图片上传的过程,并使用
Promise.allSettled()
来等待所有上传任务完成,然后根据每个任务的结果更新UI,显示上传状态。
4. 注意事项:
Promise.allSettled()
不会因为某个Promise的失败而reject,它会等待所有Promise完成。- 返回的结果数组的顺序与传入的Promise数组的顺序一致。
二、 Promise.any(): “千呼万唤始出来” 的机会主义者
Promise.any()
就像一个饥渴的“机会主义者”,只要有一个Promise成功,它就立即resolve,并返回第一个成功的Promise的值。如果所有Promise都失败了,它才会reject,并返回一个 AggregateError
错误,包含所有失败的原因。
1. 语法结构:
Promise.any(iterable);
iterable
: 一个可迭代对象,例如 Array。
2. 返回值:
一个 Promise
,resolve时返回第一个成功的Promise的值。如果所有Promise都失败,则reject,并返回一个 AggregateError
。
3. 应用场景:
-
从多个服务器获取相同的数据,只要有一个服务器返回成功,就使用该数据。
想象一下,你的应用需要从多个CDN服务器获取图片资源,为了提高可用性,你可以使用
Promise.any()
,只要有一个CDN服务器返回图片,就立即使用该图片,无需等待其他服务器。async function getImageFromCDN(cdnUrl) { try { const response = await fetch(cdnUrl); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.blob(); // 获取图片数据 } catch (error) { console.error(`从 ${cdnUrl} 获取图片失败:`, error); throw error; // 重新抛出错误,让 any 尝试下一个 CDN } } const cdnUrls = [ 'https://cdn1.example.com/image.jpg', // 假设这个CDN可能可用 'https://cdn2.example.com/image.jpg', // 假设这个CDN可能可用 'https://cdn3.example.com/image.jpg' // 假设这个CDN可能可用 ]; async function displayImage() { try { const imageData = await Promise.any(cdnUrls.map(getImageFromCDN)); const imageUrl = URL.createObjectURL(imageData); // 创建图片 URL const imageElement = document.getElementById('myImage'); // 假设有对应的 UI 元素 imageElement.src = imageUrl; // 显示图片 } catch (error) { console.error('所有 CDN 都无法获取图片:', error); // 显示默认图片或者错误信息 const imageElement = document.getElementById('myImage'); imageElement.src = 'default_image.jpg'; // 显示默认图片 } } displayImage(); // (为了让这段代码能运行,你需要创建一个简单的 HTML 页面,包含一个 id 为 "myImage" 的 <img> 元素)
在这个例子中,
Promise.any()
会尝试从多个CDN服务器获取图片,只要有一个服务器成功返回图片数据,就会立即显示该图片,提高用户体验。 -
尝试多个API接口,只要有一个接口返回有效数据,就使用该数据。
假设你的应用需要从多个支付接口获取支付信息,为了提高支付成功率,你可以使用
Promise.any()
,只要有一个接口返回有效的支付信息,就立即使用该信息,完成支付流程。async function getPaymentInfoFromAPI(apiEndpoint) { try { const response = await fetch(apiEndpoint); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!data.isValidPaymentInfo) { // 假设有一个 isValidPaymentInfo 属性 throw new Error('无效的支付信息'); } return data; } catch (error) { console.error(`从 ${apiEndpoint} 获取支付信息失败:`, error); throw error; // 重新抛出错误,让 any 尝试下一个 API } } const apiEndpoints = [ 'https://api1.example.com/payment', // 假设这个 API 可能可用 'https://api2.example.com/payment', // 假设这个 API 可能可用 'https://api3.example.com/payment' // 假设这个 API 可能可用 ]; async function processPayment() { try { const paymentInfo = await Promise.any(apiEndpoints.map(getPaymentInfoFromAPI)); // 使用 paymentInfo 完成支付流程 console.log('使用支付信息:', paymentInfo); } catch (error) { console.error('所有 API 都无法获取有效的支付信息:', error); // 显示错误信息 } } processPayment();
这段代码模拟了从多个API接口获取支付信息的过程,并使用
Promise.any()
来等待第一个返回有效支付信息的API,提高支付成功率。
4. 注意事项:
Promise.any()
只有在所有Promise都失败时才会reject,并返回一个AggregateError
。AggregateError
是一个包含所有失败原因的错误对象,你可以通过AggregateError.errors
属性访问所有错误信息。- 如果传入的
iterable
为空,Promise.any()
会直接reject,并返回一个AggregateError
。
三、 Promise.allSettled() vs Promise.any(): 兄弟间的差异化竞争
特性 | Promise.allSettled() | Promise.any() |
---|---|---|
目标 | 等待所有Promise完成,无论成功或失败,并返回所有结果。 | 只要有一个Promise成功就resolve,如果所有Promise都失败才reject。 |
成功条件 | 所有Promise都完成 (fulfilled or rejected) | 至少有一个Promise成功 (fulfilled) |
失败条件 | 不会因为某个Promise的失败而reject。 | 只有所有Promise都失败才会reject。 |
返回值 | 一个Promise,resolve时返回一个包含所有Promise结果的数组,每个结果包含状态 (status)、值 (value) 或原因 (reason)。 | 一个Promise,resolve时返回第一个成功的Promise的值,reject时返回一个 AggregateError ,包含所有失败原因。 |
适用场景 | 需要知道所有异步操作的完成状态,即使其中一些操作失败也要继续执行。例如:监控多个API请求的状态、批量更新UI并显示每个操作的结果。 | 只需要一个异步操作成功即可,例如:从多个CDN服务器获取资源、尝试多个API接口获取数据。 |
错误处理 | 需要遍历结果数组,检查每个Promise的状态,并根据状态处理错误。 | 只需要捕获 AggregateError 即可,然后通过 AggregateError.errors 属性访问所有错误信息。 |
四、 总结: “术业有专攻” ,选对工具事半功倍
Promise.allSettled()
和 Promise.any()
都是强大的并发任务管理工具,但它们的应用场景不同。
- 如果你需要全面了解所有异步操作的结果,即使其中一些操作失败也要继续执行,那么
Promise.allSettled()
是你的最佳选择。 - 如果你只需要一个异步操作成功即可,其他的操作失败可以忽略,那么
Promise.any()
更适合你。
就像一把瑞士军刀,不同的工具适用于不同的场景。 只有理解了每个工具的特性和适用场景,才能在实际开发中选择最合适的工具,提高开发效率,写出更健壮的代码。
好了,今天的讲座就到这里。 希望各位观众老爷们能够对 Promise.allSettled()
和 Promise.any()
有更深入的理解。 下次再碰到并发任务管理的问题,记得想起这两个“狠角色”哦! 咱们下期再见!