标签搜索
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 59 条评论

Promise.all

shthah
2023-03-09 / 0 评论 / 122 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2024年12月11日,已超过135天没有更新,若内容或图片失效,请留言反馈。

promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果。
promise.all中只要有一个失败了,走.catch(),在.catch()中获取第一个失败请求rejected抛出的结果。

在 Promise.all 中,如果其中任何一个 Promise 被 rejected 或抛出异常,则整个 Promise.all 将会立即被 reject,而不会等到其他 Promise 完成。

具体地说,一旦一个 Promise 失败,Promise.all 就会中止并返回一个新的拒绝状态的 Promise。然后**,其他未完成的 Promise 将会继续运行,但是它们的结果将会被忽略,不会进一步处理。

等待多个 Promise

创建一些模拟任务并逐一等待它们:

const tasks = [
  { id: 'A', durationMs: 2000, fulfills: true },
  { id: 'B', durationMs: 3000, fulfills: true },
  { id: 'C', durationMs: 1000, fulfills: true },
];
try {
  for (const task of tasks) {
    await doTask(task);
  }
  console.log('success');
} catch (e) {
  console.log('failure');
}

输出:

  1. Promise A: fulfilled ✅约 2 秒后 ( t=2)。
  2. Promise B: fulfilled ✅约 3 秒后 ( t=5)。
  3. Promise C: fulfilled ✅约 1 秒后 ( t=6)。

这段代码可以运行,但速度很慢,因为它在循环的每次迭代中一次构造一个 Promise for-of,因此未来的 Promise 只有在之前的 Promise 都已实现后才会构造。如果第一个 Promise 需要很长时间才能实现,但未来的 Promise 需要的时间较少,我们最终会浪费时间,因为我们本可以安排那些更快的 Promise 回调更早运行。事实上,在实际实现中,doTask可能会向服务器发出网络请求。使用我们当前的方法,这意味着我们一次发出一个网络请求,这不必要地慢,因为浏览器支持并发网络请求。

同步构建 Promise

提前构建所有 Promise 然后等待它们会更有效率。这样,如果 Promise 执行器函数发出网络请求,这些请求可以由运行时并发处理。

const tasks = [
  { id: 'A', durationMs: 2000, fulfills: true },
  { id: 'B', durationMs: 3000, fulfills: true },
  { id: 'C', durationMs: 1000, fulfills: true },
];

try {
  const promises = tasks.map((task) => doTask(task));
  for (const promise of promises) {
    await promise;
  }
  console.log('success');
} catch (e) {
  console.log('failure');
}

在此示例中,我们将每个任务映射到 的结果doTask,该结果返回一个 Promise。因此,我们得到了一个待定 Promises 数组:

[Promise {<pending>}, ..., Promise {<pending>}]

这一次,如果doTask浏览器发出网络请求,它可以发出多个并发请求,因为我们提前构造了所有 Promises。我们仍然会按照构造顺序一次等待一个 Promises,但不同之处在于其他网络请求可以在后台处理,而无需我们等待。

如果任何单个 Promise 拒绝,上述代码实际上会抛出未捕获的错误

解决方案:Promise.all

Promise.all接受一个 Promise 的可迭代对象并返回一个新的外部 Promise,一旦所有单个 Promise 都已实现,该外部 Promise 便会实现,而一旦单个 Promise 拒绝,该外部 Promise 便会拒绝。此外,如果Promise.all实现,它会使用所有单个已实现值的数组来实现。

try {
  const values = await Promise.all([p1, p2, ..., pn]);
  console.log('All promises fulfilled', values);
} catch (e) {
  console.log('A promise rejected');
}

Promise.all这比一次等待一个 Promises 更快,因为在调用时 Promises 已经构造好了。

const tasks = [
  { id: 'A', durationMs: 2000, fulfills: true },
  { id: 'B', durationMs: 3000, fulfills: true },
  { id: 'C', durationMs: 1000, fulfills: true },
];
try {
  await Promise.all(tasks.map((task) => doTask(task)));
  console.log('Promise.all fulfilled');
} catch (e) {
  console.log('Promise.all rejected');
}

如果 Promise 执行器函数doTask发起网络请求,我们可以看到性能的大幅提升,因为一旦构建了 Promise,浏览器就会同时执行这些网络请求。相比之下,在我们的第一个for-of循环方法中,我们必须在循环的每次迭代中一次发出一个网络请求。如果第一个请求需要很长时间才能解决,那么后续可能在很长一段时间内都不会被创建。

但是,不要误Promise.all以为这是真正的并行;Promise回调仍会在事件循环中一次排队一个,并且永远不会同时运行。队列总是一次弹出一个项目,事件循环每次只运行一件事。

失败状态

const tasks = [
  { id: 'A', durationMs: 2000, fulfills: true },
  { id: 'B', durationMs: 3000, fulfills: true },
  { id: 'C', durationMs: 1000, fulfills: false }, // rejects
];
try {
  await Promise.all(tasks.map((task) => doTask(task)));
  console.log('Promise.all fulfilled');
} catch (e) {
  console.log('Promise.all rejected');
}

Promise C: rejected ❌
Promise.all rejected
Promise A: fulfilled ✅
Promise B: fulfilled ✅

请注意 Promise C 会提前拒绝,因为我们不会等待其他 Promise 解决后再构造并等待它。但更重要的是,Promise.all根据规范,Promise C 拒绝后会立即拒绝。这很棒,因为这意味着我们不必等待其余 Promise 解决 - 只要一个 Promise 拒绝,我们就可以立即开始处理错误情况。此行为称为快速失败。

Promise.all不会立即取消Promise.all剩余的待处理 Promise。因此,剩余的 Promise 回调仍然会在事件循环中排队,并在轮到它们时运行。因此,即使在 Promise C 被拒绝后被拒绝,Promise A 和 B 仍然有机会解决。只是我们不必等待其他Promise 解决后再拒绝外部 Promise,这使我们能够更快地处理拒绝。

如果 Promise A 或 Promise B 在被拒绝后又被拒绝,会发生什么情况Promise.all?从技术上讲,它们只会拒绝Promise.all已经被拒绝的 Promise。而拒绝已解决的 Promise 不会产生任何效果。

0

评论 (0)

取消