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');
}
输出:
- Promise A: fulfilled ✅约 2 秒后 ( t=2)。
- Promise B: fulfilled ✅约 3 秒后 ( t=5)。
- 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)