并发控制:Promise.all, Promise.race 与自定义 Promise.allSettled
大家好,今天我们来深入探讨 JavaScript 中 Promise 的并发控制,重点关注 Promise.all
和 Promise.race
,以及如何实现一个自定义的 Promise.allSettled
函数。 理解这些概念对于编写高效、健壮的异步代码至关重要。
Promise.all:等待所有 Promise 完成
Promise.all
接收一个 Promise 数组(或者任何可迭代的 Promise 对象),并返回一个新的 Promise。 这个新 Promise 的行为取决于输入 Promise 的状态:
- 所有 Promise 都成功 fulfilled: 返回的 Promise 会以一个包含所有 Promise 的 fulfillment 值的数组来 fulfill。 数组元素的顺序与输入 Promise 的顺序一致。
- 任何一个 Promise rejected: 返回的 Promise 立即以第一个被 reject 的 Promise 的 reason 来 reject。
可以用一张表格来更清晰地展示:
输入 Promise 状态 | 返回 Promise 状态 | 返回值 / 原因 |
---|---|---|
所有 fulfill | fulfill | fulfillment 值数组 |
至少一个 reject | reject | 第一个 reject 的 reason |
示例:
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 100));
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // 输出: [1, 2, 3] (约 100ms 后)
})
.catch((error) => {
console.error(error); // 不会被调用
});
const promise4 = Promise.resolve(4);
const promise5 = Promise.reject('出错了');
const promise6 = Promise.resolve(6);
Promise.all([promise4, promise5, promise6])
.then((values) => {
console.log(values); // 不会被调用
})
.catch((error) => {
console.error(error); // 输出: 出错了
});
实现原理:
Promise.all
的内部实现可以大致如下:
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
const results = [];
let completedCount = 0;
if (promises.length === 0) {
return resolve(results); // 空数组的情况
}
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
// 处理非 Promise 值,将其包装成 Promise
Promise.resolve(promise)
.then((value) => {
results[i] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
})
.catch((reason) => {
reject(reason); // 只要有一个 reject,就立即 reject
});
}
});
}
// 使用示例:
myPromiseAll([promise1, promise2, promise3])
.then((values) => console.log("myPromiseAll success:", values))
.catch((error) => console.error("myPromiseAll error:", error));
myPromiseAll([promise4, promise5, promise6])
.then((values) => console.log("myPromiseAll success:", values))
.catch((error) => console.error("myPromiseAll error:", error));
关键点:
- 数组顺序:
Promise.all
保证结果数组的顺序与输入 Promise 的顺序一致。 - 短路行为: 只要有一个 Promise reject,整个
Promise.all
立即 reject。 - 非 Promise 值:
Promise.all
会将非 Promise 值包装成 Promise。Promise.resolve
实现了这个功能。 - 空数组: 如果传入一个空数组,
Promise.all
会立即 resolve,并返回一个空数组。
应用场景:
- 并行请求: 当需要同时发起多个网络请求,并且需要等待所有请求都完成后才能进行下一步操作时,可以使用
Promise.all
。 - 数据聚合: 从多个数据源获取数据,并将它们合并成一个最终结果。
- UI 更新: 等待多个动画效果完成后再更新 UI。
Promise.race:竞速 Promise
Promise.race
同样接收一个 Promise 数组(或者任何可迭代的 Promise 对象),并返回一个新的 Promise。 但与 Promise.all
不同,Promise.race
的行为取决于第一个完成的 Promise:
- 第一个 Promise fulfill: 返回的 Promise 会以第一个 fulfill 的 Promise 的值来 fulfill。
- 第一个 Promise reject: 返回的 Promise 会以第一个 reject 的 Promise 的 reason 来 reject。
同样使用表格总结:
输入 Promise 状态 | 返回 Promise 状态 | 返回值 / 原因 |
---|---|---|
第一个 fulfill | fulfill | 第一个 fulfill 的值 |
第一个 reject | reject | 第一个 reject 的 reason |
示例:
const promise7 = new Promise((resolve) => setTimeout(() => resolve(7), 50));
const promise8 = new Promise((resolve) => setTimeout(() => resolve(8), 100));
Promise.race([promise7, promise8])
.then((value) => {
console.log(value); // 输出: 7 (约 50ms 后)
})
.catch((error) => {
console.error(error); // 不会被调用
});
const promise9 = new Promise((resolve, reject) => setTimeout(() => reject('超时'), 20));
const promise10 = new Promise((resolve) => setTimeout(() => resolve(10), 100));
Promise.race([promise9, promise10])
.then((value) => {
console.log(value); // 不会被调用
})
.catch((error) => {
console.error(error); // 输出: 超时 (约 20ms 后)
});
实现原理:
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
Promise.resolve(promise)
.then(resolve) // 只要有一个 fulfill,就立即 resolve
.catch(reject); // 只要有一个 reject,就立即 reject
}
});
}
// 使用示例:
myPromiseRace([promise7, promise8])
.then((value) => console.log("myPromiseRace success:", value))
.catch((error) => console.error("myPromiseRace error:", error));
myPromiseRace([promise9, promise10])
.then((value) => console.log("myPromiseRace success:", value))
.catch((error) => console.error("myPromiseRace error:", error));
关键点:
- 竞速:
Promise.race
只关心第一个完成的 Promise。 - 短路行为: 只要有一个 Promise fulfill 或 reject,整个
Promise.race
立即 fulfill 或 reject。 - 非 Promise 值:
Promise.race
会将非 Promise 值包装成 Promise。 - 空数组: 如果传入一个空数组,
Promise.race
返回的 Promise 将永远 pending,因为它永远不会有 resolve 或者 reject 的机会。
应用场景:
- 超时控制: 可以用
Promise.race
来实现 Promise 的超时机制。 如果在指定时间内 Promise 没有完成,就 reject 该 Promise。 - 备选方案: 当有多个备选方案时,可以使用
Promise.race
来选择最先完成的方案。 - 取消操作: 可以用一个 reject 的 Promise 来取消一个正在进行的 Promise。
Promise.allSettled:兼顾成功与失败
Promise.allSettled
接收一个 Promise 数组(或者任何可迭代的 Promise 对象),并返回一个新的 Promise。 与 Promise.all
不同,Promise.allSettled
会等待所有 Promise 都完成(无论 fulfill 还是 reject)后才 resolve。 返回的 Promise 会以一个包含每个 Promise 结果的数组来 fulfill。
数组中的每个元素都是一个对象,包含以下属性:
status
:字符串,表示 Promise 的状态,可以是"fulfilled"
或"rejected"
。value
:如果 Promise fulfill,则包含 fulfillment 值。reason
:如果 Promise reject,则包含 rejection reason。
用表格描述:
输入 Promise 状态 | 返回 Promise 状态 | 返回值 |
---|---|---|
所有 fulfill 或 reject | fulfill | 包含每个 Promise 结果的数组 (status, value/reason) |
示例:
const promise11 = Promise.resolve(11);
const promise12 = Promise.reject('出错了');
const promise13 = new Promise((resolve) => setTimeout(() => resolve(13), 50));
Promise.allSettled([promise11, promise12, promise13])
.then((results) => {
console.log(results);
// 输出:
// [
// { status: 'fulfilled', value: 11 },
// { status: 'rejected', reason: '出错了' },
// { status: 'fulfilled', value: 13 }
// ]
});
实现原理:
function myPromiseAllSettled(promises) {
return new Promise((resolve) => {
if (!Array.isArray(promises)) {
return resolve(new TypeError('Argument must be an array')); // allSettled 应当 resolve 而不是 reject
}
const results = [];
let completedCount = 0;
if (promises.length === 0) {
return resolve(results); // 空数组的情况
}
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
Promise.resolve(promise)
.then((value) => {
results[i] = { status: 'fulfilled', value: value };
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
})
.catch((reason) => {
results[i] = { status: 'rejected', reason: reason };
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
});
}
});
}
// 使用示例:
myPromiseAllSettled([promise11, promise12, promise13])
.then((results) => console.log("myPromiseAllSettled:", results));
关键点:
- 不短路:
Promise.allSettled
会等待所有 Promise 都完成,无论 fulfill 还是 reject。 - 结果格式: 返回的结果数组中的每个元素都包含
status
、value
和reason
属性。 - 非 Promise 值:
Promise.allSettled
会将非 Promise 值包装成 Promise。 - 空数组: 如果传入一个空数组,
Promise.allSettled
会立即 resolve,并返回一个空数组。
应用场景:
- 错误处理: 当需要执行多个操作,并且希望知道哪些操作成功,哪些操作失败时,可以使用
Promise.allSettled
。 即使某些操作失败,也需要继续执行其他操作。 - 审计日志: 记录所有操作的结果,包括成功和失败的操作。
- 资源清理: 无论操作成功与否,都需要执行一些清理工作。
总结:并发控制,各有侧重
Promise.all
, Promise.race
, 和 Promise.allSettled
提供了不同的并发控制策略。 Promise.all
等待所有 Promise 成功,Promise.race
关注最先完成的 Promise,而 Promise.allSettled
兼顾所有 Promise 的结果,提供了更全面的控制和错误处理能力。
深入理解,灵活应用
理解 Promise.all
、Promise.race
和 Promise.allSettled
的原理和应用场景,可以帮助我们编写更高效、更健壮的异步代码。 根据不同的需求选择合适的并发控制策略,可以提高代码的性能和可维护性。
灵活组合,解决问题
这些并发控制方法可以灵活组合使用,解决更复杂的问题。 例如,可以使用 Promise.race
实现超时机制,并结合 Promise.allSettled
来处理超时后的其他 Promise 的结果。