本文将深入探讨 Promise 的两个核心静态方法:Promise.all
和 Promise.allSettled
。相信你已经熟悉了它们的基本用法,但你是否曾好奇过,在这些便捷的 API 背后,究竟隐藏着怎样的实现机制?
当我们需要并行处理多个异步任务时,这两个方法成为了我们最得力的助手。Promise.all
秉承"全赢或全输"的果断哲学,而 Promise.allSettled
则展现了"有始有终"的包容智慧。理解它们的底层实现,不仅能让我们更加自信地运用它们,更是一次对 Promise 机制的深度探索之旅。
今天,就让我们亲手揭开它们的神秘面纱,从零开始,用代码实现这两个强大的工具。
📢 说明:本文涉及的某些函数或方法(如
definePropertyConfig
)未在此处定义,其完整实现可参阅本专栏的往期文章。
搭建基本框架
js
definePropertyConfig(Promise, "all", function (iterable) {
/* 此处实现静态方法 all */
});
definePropertyConfig(Promise, "allSettled", function (iterable) {
/* 此处实现静态方法 allSettled */
});
Promise.all实现
根据 MDN 的定义,Promise.all
具有以下特性:
从上述描述中,我们可以提取出关键信息:
Promise.all
接收一个 Promise 可迭代对象作为参数- 当所有 Promise 都成功完成时,返回一个包含所有兑现值的数组
- 只要有一个 Promise 失败,立即返回该失败原因
参数验证
让我们从参数验证开始实现 Promise.all
。需要特别注意以下几点:
- 调用者必须是 Promise 构造函数本身
- 参数必须是一个可迭代对象
- 最终返回一个新的 Promise 对象 基于以上考虑,代码实现如下:
js
definePropertyConfig(Promise, "all", function (iterable) {
// 验证调用者身份
if (!(this === Promise)) {
throw new TypeError("Promise.all called on non-constructor");
}
// 验证参数是否为可迭代对象
if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(
`${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`
);
}
return new Promise((resolve, reject) => {
/* 此处实现核心逻辑 */
});
});
初始化变量
在实现核心逻辑之前,我们需要准备以下变量:
results
: 按顺序保存所有 Promise 的结果completed
: 已完成的 Promise 数量count
: Promise 的总数isRejected
: 标记是否已有 Promise 被拒绝
js
definePropertyConfig(Promise, "all", function (iterable) {
/* 参数验证代码已省略 */
return new Promise((resolve, reject) => {
var isRejected = false; // 标记是否已被拒绝
var results = []; // 存储兑现值
let count = 0; // Promise 总数
let completed = 0; // 已完成的 Promise 数量
});
});
处理边界情况
在继续实现核心逻辑之前,我们需要考虑一些边界情况:
js
definePropertyConfig(Promise, "all", function (iterable) {
/* 参数验证代码已省略 */
return new Promise((resolve, reject) => {
var isRejected = false; // 标记是否被reject
var results = []; // 存储兑现值
var count = 0; // 记录兑现数量
var completed = 0; // 记录已经完成数量
var promises = Array.from(iterable); // 将可迭代对象转换为数组
var = promises.length;
// 如果数组为空直接返回results
if (count === 0) {
Promise.resolve(results);
return;
}
/* 核心逻辑将在下一步实现 */
});
});
核心逻辑实现
js
definePropertyConfig(Promise, "all", function (iterable) {
/* 参数验证代码已省略 */
return new Promise((resolve, reject) => {
/* 变量声明和边界处理已省略 */
// 遍历数组
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
if (isRejected) return; // 存在失败的则不再处理
results[index] = value; // 将兑现值按照index对号入座存储起来,由于闭包的缘故,index不会存在错乱的问题
completed += 1; // 记录已经兑现的数量
// 比对已经兑现数量是否数组相等,相等表示已经完成,返回兑现值数组
if (completed === count) {
resolve(results);
}
})
.catch((reason) => {
if (isRejected) return; // 存在失败的则不再处理
isRejected = true; // 如果有失败的 则记录为true
reject(reason); // 返回失败原因
});
});
});
});
完整实现代码
js
definePropertyConfig(Promise, "all", function (iterable) {
if (!(this === Promise)) {
throw new TypeError("PromiseReject called on non-object");
}
if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(
"promises is not iterable (cannot read property Symbol(Symbol.iterator))"
);
}
return new Promise((resolve, reject) => {
var isRejected = false; // 标记是否被reject
var results = []; // 存储兑现值
var count = 0; // 记录兑现数量
var completed = 0; // 记录已经完成数量
var promises = Array.from(iterable);
var = promises.length;
if (count === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
if (isRejected) return;
results[index] = value;
completed += 1;
if (completed === count) {
resolve(results);
}
})
.catch((reason) => {
if (isRejected) return;
isRejected = true;
reject(reason);
});
});
});
});
Promise.allSettled实现
理解 allSettled 的特性
与 Promise.all
不同,Promise.allSettled
会等待所有 Promise 完成(无论是兑现还是拒绝),并返回一个包含所有结果的对象数组。每个结果对象都有以下结构:
- 对于兑现的 Promise:
{ status: "fulfilled", value: <兑现值> }
- 对于拒绝的 Promise:
{ status: "rejected", reason: <拒绝原因> }
参数验证和边界处理
在参数验证和边界处理与Promise.all
是相同的
js
definePropertyConfig(Promise, "allSettled", function (iterable) {
if (!(this === Promise)) {
throw new TypeError("Promise.allSettled called on non-object");
}
if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(
"promises is not iterable (cannot read property Symbol(Symbol.iterator))"
);
}
return new Promise((resolve) => {
var results = [];
var count = 0;
var completed = 0;
const promises = Array.from(iterable);
count = promises.length;
if (count === 0) {
resolve(results);
return;
}
/* 核心逻辑将在下一步实现 */
});
});
核心逻辑实现
js
definePropertyConfig(Promise, "allSettled", function (iterable) {
/* 参数验证代码已省略 */
return new Promise((resolve) => {
/* 变量声明和边界处理省略 */
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = { status: FULFILLED, value: value };
completed += 1;
if (completed === count) {
resolve(results);
}
})
.catch((reason) => {
results[index] = { status: REJECTED, reason };
completed += 1;
if (completed === count) {
resolve(results);
}
});
});
});
});
完整实现代码
js
definePropertyConfig(Promise, "allSettled", function (iterable) {
if (!(this === Promise)) {
throw new TypeError("Promise.allSettled called on non-object");
}
if (iterable == null || typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(
"promises is not iterable (cannot read property Symbol(Symbol.iterator))"
);
}
return new Promise((resolve) => {
var results = [];
var count = 0;
var completed = 0;
const promises = Array.from(iterable);
count = promises.length;
if (count === 0) {
Promise.resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = { status: FULFILLED, value: value };
completed += 1;
if (completed === count) {
resolve(results);
}
})
.catch((reason) => {
results[index] = { status: REJECTED, reason };
completed += 1;
if (completed === count) {
resolve(results);
}
});
});
});
});
小结
通过亲手实现 Promise.all
和 Promise.allSettled
,我们深入理解了这两个重要方法的内部机制:
核心洞察
-
设计哲学差异:
Promise.all
采用"全赢或全输"的策略,适合相互依赖的异步操作Promise.allSettled
采用"有始有终"的策略,适合相互独立的异步操作
-
关键技术要点:
- 闭包的应用:通过闭包保存索引,确保结果顺序与输入顺序一致
- Promise 规范化 :使用
Promise.resolve()
处理各种输入类型 - 状态管理:使用计数器精确判断所有 Promise 的完成状态
- 错误处理 :
Promise.all
的快速失败机制 vsPromise.allSettled
的全面收集
-
实现模式:
- 参数验证和边界处理是健壮性的基础
- 异步并发控制通过计数器和状态标志实现
- 结果收集利用数组索引和闭包特性
实践价值
理解这些底层实现不仅帮助我们更自信地使用这些 API,更重要的是:
- 当遇到复杂异步场景时,能够选择最合适的工具
- 在调试异步问题时,能够快速定位问题根源
- 为实现更复杂的异步控制流打下坚实基础
这两个方法的实现体现了 JavaScript 异步编程的精髓:通过简单的模式组合,构建出强大的并发处理能力。掌握它们,你就掌握了处理复杂异步场景的重要工具。