发布于 2026-01-06 0 阅读
0

使用 async/await 处理数组中的 Promise DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

处理数组中的 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);
  })
};
Enter fullscreen mode Exit fullscreen mode

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);
})();
Enter fullscreen mode Exit fullscreen mode

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);
})();
Enter fullscreen mode Exit fullscreen mode

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);
  }
})();
Enter fullscreen mode Exit fullscreen mode

更新:以下方法是原帖中的方法,但经过多年的实践经验,我意识到这种方法不必要地复杂化了事情,我只是为了实现循环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());
})();
Enter fullscreen mode Exit fullscreen mode

这比之前的实现方式要复杂一些,但我会另写一篇文章来解释。这篇文章就只提供一些快速参考吧😉。

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)
  }
})();
Enter fullscreen mode Exit fullscreen mode

更新:再次说明,以下方法是最初帖子中的方法,但经过多年的实践经验,我意识到这种方法不必要地复杂化了事情,我只是为了实现循环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());
})();
Enter fullscreen mode Exit fullscreen mode

请注意,我在这里是通过硬编码的方式构建异步函数批次。在实际应用中,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));
Enter fullscreen mode Exit fullscreen mode

或者,您也可以选择使用一些库来lodash帮助您完成这项任务。

import chunk from 'lodash.chunk';

// outputs [[1, 2, 3], [4, 5, 6]]
console.log(chunk([1, 2, 3, 4, 5, 6], 3));
Enter fullscreen mode Exit fullscreen mode

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