处理数组中的 Promise(使用 async/await)
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
Promiseasync/await是新版 JavaScript 的一项非常受欢迎的新增功能。如果你还没用过它,还深陷回调地狱,不妨了解一下,并尽快开始使用它。相信我,它非常棒!MDN 文档是个不错的起点,CSS-Tricks上也有一篇很棒的文章。
async/await但是,当需要处理多个 Promise时,可能会有点棘手。幸运的是,我根据自身经验整理了一份处理 Promise 的速查表。
PS:未使用任何外部库! 😉
现在,让我们开始吧!假设我们有以下异步函数:
const resolveInTwoSeconds = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(2), 2000);
})
};
const resolveInThreeSeconds = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(3), 3000);
})
};
const resolveInFiveSeconds = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(5), 5000);
})
};
1. 使用 Promise.all 等待所有承诺完成
Promise.all接受一个 Promise 数组,并返回一个新的 Promise,该 Promise 仅在数组中的所有 Promise 都已解析后才会解析。该 Promise 解析为一个数组,其中包含每个 Promise 返回的所有值。
(async function() {
const asyncFunctions = [
resolveInTwoSeconds(),
resolveInThreeSeconds(),
resolveInFiveSeconds()
];
const results = await Promise.all(asyncFunctions);
// outputs `[2, 3, 5]` after five seconds
console.log(results);
})();
2. 使用 Promise.race 等待至少一个 Promise 完成。
Promise.race接受一个 promise 数组,并返回一个新的 promise,当数组中的一个 promise 被解决时,该新的 promise 会立即解决,其值为该 promise 的值。
(async function() {
const asyncFunctions = [
resolveInTwoSeconds(),
resolveInThreeSeconds(),
resolveInFiveSeconds()
];
const result = await Promise.race(asyncFunctions);
// outputs `2` after two seconds
console.log(result);
})();
3. 等待所有承诺逐一兑现。
实现这一点的最简单、最直接的方法是使用普通的for循环。它易于阅读和理解。
(async function() {
const asyncFunctions = [resolveInTwoSeconds, resolveInThreeSeconds, resolveInFiveSeconds];
// outputs 2 after 2 seconds
// outputs 3 after 5 seconds
// outputs 5 after 8 seconds
for (const asyncFn of asyncFunctions) {
const result = await asyncFn();
console.log(result);
}
})();
更新:以下方法是原帖中的方法,但经过多年的实践经验,我意识到这种方法不必要地复杂化了事情,我只是为了实现循环reduce效果而做的权宜之计for。建议:直接使用 for 循环即可。如果你感兴趣,我仍然保留了这种方法。
类本身没有Promise可以快速完成此操作的原生方法,但我们可以使用Array.prototype.reduce方法来实现目标。
(async function() {
const asyncFunctions = [resolveInTwoSeconds, resolveInThreeSeconds, resolveInFiveSeconds];
// outputs 2 after 2 seconds
// outputs 3 after 5 seconds
// outputs 5 after 8 seconds
await asyncFunctions.reduce(async (previousPromise, nextAsyncFunction) => {
await previousPromise;
const result = await nextAsyncFunction();
console.log(result);
}, Promise.resolve());
})();
这比之前的实现方式要复杂一些,但我会另写一篇文章来解释。这篇文章就只提供一些快速参考吧😉。
4. 分批运行异步函数,每批函数并行执行。
如果您想避免触及某些 API 服务的速率限制,这将非常有用。它利用了与 #3 中相同的概念,即一个按顺序解析的 Promise 数组,并结合了二维 Promise 数组和Promise.all.
关键在于首先构建一个二维数组,其中包含所有异步函数。构建完成后,我们可以遍历每个异步函数集合并并行执行它们,并等待Promise.all每个函数完成。在当前批次中的所有 Promise 都完成之前,我们不会处理下一批。
(async function() {
const asyncFunctionsInBatches = [
[resolveInTwoSeconds, resolveInTwoSeconds],
[resolveInThreeSeconds, resolveInThreeSeconds],
[resolveInFiveSeconds, resolveInFiveSeconds],
];
// Outputs [2, 2] after two seconds
// Outputs [3, 3] after five seconds
// Outputs [5, 5] after eight seconds
for (const currentBatch of asyncFunctionsInBatches) {
const currentBatchPromises = currentBatch.map(asyncFn => asyncFn())
const batchResults = await Promise.all(currentBatchPromises)
console.log(batchResults)
}
})();
更新:再次说明,以下方法是最初帖子中的方法,但经过多年的实践经验,我意识到这种方法不必要地复杂化了事情,我只是为了实现循环reduce效果而做的权宜之计for。建议:直接使用 for 循环即可。如果你感兴趣,我仍然保留了这段代码。
以下是上述概念的完整实现:
(async function() {
const asyncFunctionsInBatches = [
[resolveInTwoSeconds, resolveInTwoSeconds],
[resolveInThreeSeconds, resolveInThreeSeconds],
[resolveInFiveSeconds, resolveInFiveSeconds],
];
// Outputs [2, 2] after two seconds
// Outputs [3, 3] after five seconds
// Outputs [5, 5] after eight seconds
await asyncFunctionsInBatches.reduce(async (previousBatch, currentBatch, index) => {
await previousBatch;
console.log(`Processing batch ${index}...`);
const currentBatchPromises = currentBatch.map(asyncFunction => asyncFunction())
const result = await Promise.all(currentBatchPromises);
console.log(result);
}, Promise.resolve());
})();
请注意,我在这里是通过硬编码的方式构建异步函数批次。在实际应用中,API 调用或其他类似操作可能会返回长度动态变化的数组,因此您需要自行拆分它们。以下是一个快速实现示例:
const splitInBatch = (arr, batchSize) => {
return arr.reduce((accumulator, element, index) => {
const batchIndex = Math.floor(index / batchSize);
if (Array.isArray(accumulator[batchIndex])) {
accumulator[batchIndex].push(element);
} else {
accumulator.push([element]);
}
return accumulator;
}, []);
}
// outputs [[1, 2, 3], [4, 5, 6]]
console.log(splitInBatch([1, 2, 3, 4, 5, 6], 3));
或者,您也可以选择使用一些库来lodash帮助您完成这项任务。
import chunk from 'lodash.chunk';
// outputs [[1, 2, 3], [4, 5, 6]]
console.log(chunk([1, 2, 3, 4, 5, 6], 3));
5. 额外提示:不要将异步函数传递给 forEach。
Array.prototype.map记住,`async`和 `async`的区别Array.prototype.forEach在于后者不会返回每次迭代的结果。如果我们把async函数传递给forEach`async`,我们就无法获取返回的 Promise 来进行任何有用的操作。除非你想执行异步函数后就置之不理,否则forEach永远不要把异步函数传递给 `async`。
结论
好了!以上就是关于如何处理 Promise 数组的全部 5 个速查表。希望对大家有所帮助😁,如果有什么需要改进的地方,请务必在评论区告诉我。
再见!
文章来源:https://dev.to/afifsohaili/dealing-with-promises-in-an-array-with-async-await-5d7g