在前端异步编程中,Promise 的静态方法扮演着至关重要的角色。它们为我们处理多个异步操作提供了简洁而强大的工具,让复杂的异步流程变得清晰可控。本文将深入剖析 Promise 常用静态方法的实现原理,带你一步步手写这些方法,彻底掌握它们的工作机制。
认识 Promise 静态方法
Promise 对象提供了多个静态方法,这些方法都是直接通过Promise构造函数调用的,无需实例化。它们的主要作用是对多个 Promise 实例进行组合操作,简化异步流程控制。
最常用的 Promise 静态方法包括:
- Promise.all():等待所有 Promise 完成并收集结果
- Promise.allSettled():等待所有 Promise 完成,无论成功失败
- Promise.race():返回第一个完成的 Promise 结果
- Promise.any():返回第一个成功的 Promise 结果
这些方法在实际开发中应用广泛,比如并发请求数据、设置请求超时、处理多个异步操作的结果等场景都离不开它们。
手写 Promise.resolve () 与 Promise.reject ()
作为最基础的 Promise 静态方法,resolve和reject用于快速创建处于特定状态的 Promise 实例。
Promise.resolve () 实现
Promise.resolve()的作用是返回一个处于 "已完成"(fulfilled) 状态的 Promise 实例,它接收一个参数作为完成值。如果参数本身就是一个 Promise 实例,则直接返回该实例。
javascript
Promise.myResolve = function(value) {
// 如果参数是Promise实例,直接返回
if (value instanceof Promise) {
return value;
}
// 否则返回一个新的已完成Promise
return new Promise((resolve) => {
resolve(value);
});
};
这个实现非常直接:首先检查传入的值是否已经是 Promise 实例,如果是则直接返回;否则创建一个新的 Promise 并立即用该值完成它。
Promise.reject () 实现
Promise.reject()与resolve类似,但它返回的是一个处于 "已拒绝"(rejected) 状态的 Promise 实例。
javascript
Promise.myReject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
};
注意reject方法不会像resolve那样检查参数是否为 Promise 实例,无论传入什么值都会作为拒绝原因。
手写 Promise.all ()
Promise.all()是最常用的 Promise 组合方法之一,它接收一个 Promise 数组 (或可迭代对象),返回一个新的 Promise。当所有输入的 Promise 都完成时,新 Promise 才会完成,结果是一个包含所有完成值的数组;如果有任何一个输入的 Promise 被拒绝,新 Promise 会立即被拒绝。
实现思路
- 接收一个可迭代对象作为参数,通常是数组
- 返回一个新的 Promise 实例
- 遍历输入的所有 Promise
- 维护一个计数器和结果数组,记录已完成的 Promise 数量和结果
- 当所有 Promise 都完成时,用结果数组完成新 Promise
- 如果有任何一个 Promise 被拒绝,立即用该原因拒绝新 Promise
代码实现
javascript
Promise.myAll = function (promises) {
// 返回一个新的Promise实例
return new Promise((resolve, reject) => {
// 存储所有成功的结果,按传入数组的顺序排列
let results = [];
// 记录传入的Promise总数量(用于判断是否所有Promise都已处理)
let count = 0;
// 记录已成功完成的Promise数量
let fulfilledCount = 0;
for (const p of promises) {
let i = count++;
// 将当前值包装为Promise(处理非Promise值的情况,如普通数字、字符串等)
Promise.resolve(p)
.then((data) => {
results[i] = data;
fulfilledCount++;
// 当所有Promise都成功时(已成功数量 === 总数量)
if (fulfilledCount === count) {
// 用结果数组resolve外部Promise
resolve(results);
}
},
// 当当前Promise失败时的回调:直接调用外部Promise的reject
// 符合Promise.all"一错即错"的特性,立即返回第一个失败原因
reject);
}
// 特殊情况:如果传入的是空数组,直接resolve空数组
// 此时循环未执行,count保持0,直接返回空结果
if (count === 0) {
resolve(results);
}
});
};
关键细节
- 保持结果数组的顺序与输入数组一致,即使某些 Promise 先完成
- 只要有一个 Promise 被拒绝,就会立即触发拒绝,且只触发一次
- 输入数组中的非 Promise 值会被Promise.resolve自动转换
手写 Promise.race ()
Promise.race()的行为类似于 "赛跑",它接收一个 Promise 数组,返回一个新的 Promise。这个新 Promise 会跟随第一个完成 (无论是完成还是拒绝) 的 Promise,采用它的结果或原因。
实现思路
- 接收一个可迭代对象作为参数
- 返回一个新的 Promise 实例
- 遍历输入的所有 Promise
- 一旦有任何一个 Promise 完成或拒绝,就用它的结果或原因处理新 Promise
代码实现
javascript
Promise.myRace = function (promises) {
return new Promise((resolve, reject) => {
for (const p of promises) {
Promise.resolve(p).then(resolve, reject);
}
})
}
典型应用:请求超时处理
race方法非常适合实现请求超时功能:
javascript
// 模拟一个异步请求
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => resolve('数据加载成功'), 3000);
});
}
// 超时控制器
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), ms);
});
}
// 使用race实现超时控制
Promise.myRace([fetchData(), timeout(2000)])
.then(console.log)
.catch(console.error); // 会输出"请求超时"
手写 Promise.allSettled ()
Promise.allSettled()等待所有输入的 Promise 都 "落定"(settled),即无论是完成还是拒绝,都会等待所有 Promise 处理完毕。它返回的 Promise 会完成一个数组,每个元素表示对应 Promise 的结果,包含状态和值 / 原因。
实现思路
- 接收一个可迭代对象作为参数
- 返回一个新的 Promise 实例
- 遍历输入的所有 Promise
- 每个 Promise 完成或拒绝后,都记录其状态和结果
- 当所有 Promise 都落定后,用结果数组完成新 Promise
代码实现
javascript
Promise.myAllSettled = function(promises){
let results = [];
for (const p of promises) {
results.push(Promise.resolve(p).then((data) => ({
status:"fulfilled",
data
}),
(reason) => ({
status:"rejected",
reason
})
))
}
return Promise.all(results);
}
应用场景
allSettled特别适合需要知道所有异步操作结果的场景,比如批量处理数据时,即使某些操作失败,也需要知道成功的部分并继续处理。
手写 Promise.any ()
Promise.any()与all相反,它等待第一个 "成功完成" 的 Promise。只要有一个输入的 Promise 成功完成,它就会用该结果完成;如果所有输入的 Promise 都被拒绝,它会用一个包含所有拒绝原因的AggregateError拒绝。
实现思路
- 接收一个可迭代对象作为参数
- 返回一个新的 Promise 实例
- 遍历输入的所有 Promise
- 只要有一个 Promise 成功完成,就用该结果完成新 Promise
- 如果所有 Promise 都被拒绝,收集所有原因并拒绝新 Promise
代码实现
javascript
Promise.myAny = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
const errors = []; // 收集所有拒绝原因
let rejectedCount = 0;
if (promises.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
promises.forEach((promise) => {
Promise.myResolve(promise).then(
(value) => {
// 第一个成功的Promise会触发resolve
resolve(value);
},
(reason) => {
errors.push(reason);
rejectedCount++;
// 所有Promise都被拒绝时
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
};
与 race 的区别
any和race都关注 "第一个有结果的 Promise",但有重要区别:
- race返回第一个 "落定" 的 Promise,无论成功还是失败
- any返回第一个 "成功完成" 的 Promise,忽略拒绝的 Promise,直到所有都拒绝才会拒绝
测试与验证
实现完成后,我们需要验证这些静态方法是否符合预期行为。以myAll为例:
ini
// 测试Promise.myAll
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = 3;
Promise.myAll([p1, p2, p3]).then(result => {
console.log(result); // [1, 2, 3]
});
// 测试拒绝情况
const p4 = Promise.reject('出错了');
Promise.myAll([p1, p4, p2]).catch(reason => {
console.log(reason); // '出错了'
});
同样的方式可以测试其他方法,确保它们的行为与原生 Promise 静态方法一致。
总结
通过手写这些 Promise 静态方法,我们深入理解了它们的内部工作机制:
- all等待所有完成,或第一个拒绝
- allSettled等待所有落定,收集所有结果
- race返回第一个落定的结果
- any等待第一个成功,或所有拒绝
掌握这些方法的实现原理,不仅能帮助我们更好地在项目中应用它们,还能提升我们对异步编程的理解。在实际开发中,选择合适的 Promise 静态方法可以大大简化异步流程控制,写出更清晰、更健壮的代码。
如果大家在学习和使用 Promise 的过程中有任何问题或心得,欢迎在评论区留言交流,也可以前往我的github 网站查看更多关于 Promise 的内容。
如果您觉得这篇文章对您有帮助,欢迎点赞和收藏,大家的支持是我继续创作优质内容的动力🌹🌹🌹也希望您能在😉😉😉我的主页 😉😉😉找到更多对您有帮助的内容。
- 致敬每一位赶路人