嘿,大家好!我是你们今天的 Promise 解说员。今天咱们来聊聊 Promise 的两个重量级选手:Promise.all
和 Promise.race
。这两个家伙在处理多个 Promise 时,能发挥巨大的作用。别害怕,我会用最简单、最幽默的方式,带你彻底搞懂它们,并且手写实现它们!
开场:Promise 的小秘密
在深入 Promise.all
和 Promise.race
之前,先简单回顾一下 Promise 的基本概念。Promise 代表一个异步操作的最终完成 (或失败) 及其结果值。 它有三种状态:
- pending (进行中):初始状态,既没有被兑现,也没有被拒绝。
- fulfilled (已兑现):操作成功完成。
- rejected (已拒绝):操作失败。
第一幕:Promise.all
– 团队协作的力量
Promise.all
就像一个团队的队长,它会等待所有队员(Promise)都完成任务,然后才会宣布整个团队任务完成。如果其中任何一个队员失败了,队长就会直接宣布整个团队任务失败。
Promise.all
的规则:
- 输入: 接收一个 Promise 数组(或者任何 iterable 对象,比如 Set, Map 等,只要里面的元素都是 Promise-like 对象)。
- 输出: 返回一个新的 Promise。
- 成功: 当输入的所有 Promise 都成功 fulfilled 时,返回的 Promise 也会被 fulfilled,并且它的 value 是一个包含所有 Promise 的 fulfillment 值的数组,数组的顺序和输入 Promise 的顺序一致。
- 失败: 只要输入中的任何一个 Promise 被 rejected,返回的 Promise 就会立即被 rejected,并且它的 reason 就是第一个被 rejected 的 Promise 的 reason。
Promise.all
的使用场景:
- 并行请求: 同时发起多个 API 请求,等待所有请求都完成后,再进行下一步操作。
- 资源加载: 同时加载多个资源文件(例如图片、脚本、样式),等待所有资源都加载完成后,再渲染页面。
手写实现 Promise.all
:
现在,让我们撸起袖子,手写一个 Promise.all
。 这次咱们用 ES6 的语法,让代码更简洁易懂。
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
// 1. 参数校验:确保传入的是一个可迭代对象
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'));
}
// 2. 将可迭代对象转换为数组 (防止传入的是类数组对象)
const promiseArray = Array.from(promises);
// 3. 如果传入的是空数组,则立即 resolve 一个空数组
if (promiseArray.length === 0) {
return resolve([]);
}
// 4. 用于存储结果的数组
const results = [];
// 5. 用于记录已完成的 Promise 的数量
let completedCount = 0;
// 6. 遍历 Promise 数组
for (let i = 0; i < promiseArray.length; i++) {
const promise = promiseArray[i];
// 7. 确保数组中的每一项都是一个 Promise 对象,如果不是,则将其包装成 Promise
Promise.resolve(promise).then(
(value) => {
// 8. 成功回调
results[i] = value; // 按照顺序存储结果
completedCount++;
// 9. 当所有 Promise 都成功 fulfilled 时,resolve 整个 Promise
if (completedCount === promiseArray.length) {
resolve(results);
}
},
(reason) => {
// 10. 失败回调:只要有一个 Promise 被 rejected,就立即 reject 整个 Promise
reject(reason);
}
);
}
});
}
代码解释:
- 参数校验: 检查传入的参数
promises
是否是可迭代对象。如果不是,则 reject 一个TypeError
。 - 转换为数组: 将传入的
promises
转换为数组,方便后续处理。 - 空数组处理: 如果传入的是空数组,则直接 resolve 一个空数组。这是
Promise.all
的一个特殊行为。 - 结果数组:
results
数组用于存储每个 Promise 的 fulfillment 值。 - 计数器:
completedCount
变量用于记录已完成的 Promise 的数量。 - 循环遍历: 遍历 Promise 数组,对每个 Promise 进行处理。
- Promise.resolve包装: 使用
Promise.resolve(promise)
将数组中的每一项都包装成 Promise 对象。这样可以处理数组中包含非 Promise 值的情况。 - 成功回调: 当一个 Promise 成功 fulfilled 时,将它的 fulfillment 值存储到
results
数组中,并将completedCount
加 1。 - 全部完成: 当
completedCount
等于 Promise 数组的长度时,表示所有 Promise 都已成功 fulfilled,此时 resolve 整个 Promise,并将results
数组作为 fulfillment 值传递给 resolve 函数。 - 失败回调: 只要有一个 Promise 被 rejected,就立即 reject 整个 Promise,并将第一个被 rejected 的 Promise 的 reason 传递给 reject 函数。
测试一下:
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 100));
const promise3 = Promise.resolve(3);
myPromiseAll([promise1, promise2, promise3])
.then((values) => {
console.log('Success:', values); // 输出: Success: [ 1, 2, 3 ]
})
.catch((error) => {
console.error('Error:', error);
});
const promise4 = Promise.reject('Error 4');
const promise5 = Promise.resolve(5);
myPromiseAll([promise4, promise5])
.then((values) => {
console.log('Success:', values);
})
.catch((error) => {
console.error('Error:', error); // 输出: Error: Error 4
});
第二幕:Promise.race
– 速度与激情
Promise.race
就像一场赛跑比赛,它会等待第一个 Promise 完成(无论是 fulfilled 还是 rejected),然后立即返回该 Promise 的结果。 就像百米赛跑,谁先冲过终点线,就以谁的结果为准。
Promise.race
的规则:
- 输入: 接收一个 Promise 数组(或者任何 iterable 对象,只要里面的元素都是 Promise-like 对象)。
- 输出: 返回一个新的 Promise。
- 结果: 一旦数组中的某个 Promise fulfilled 或 rejected,返回的 Promise 也会立即 fulfilled 或 rejected,并且它的 value 或 reason 就是第一个 fulfilled 或 rejected 的 Promise 的 value 或 reason。
Promise.race
的使用场景:
- 超时控制: 设置一个超时 Promise,如果请求在指定时间内没有返回,就 reject 该 Promise。
- 竞争资源: 多个 Promise 竞争同一个资源,哪个 Promise 先拿到资源,就使用哪个 Promise 的结果。
手写实现 Promise.race
:
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
// 1. 参数校验:确保传入的是一个可迭代对象
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'));
}
// 2. 将可迭代对象转换为数组
const promiseArray = Array.from(promises);
// 3. 如果传入的是空数组,则返回一个永远pending的Promise
if (promiseArray.length === 0) {
return; // 返回一个永远pending的Promise
}
// 4. 遍历 Promise 数组
for (let i = 0; i < promiseArray.length; i++) {
const promise = promiseArray[i];
// 5. 确保数组中的每一项都是一个 Promise 对象
Promise.resolve(promise).then(
(value) => {
// 6. 只要有一个 Promise 成功 fulfilled,就立即 resolve 整个 Promise
resolve(value);
},
(reason) => {
// 7. 只要有一个 Promise 被 rejected,就立即 reject 整个 Promise
reject(reason);
}
);
}
});
}
代码解释:
- 参数校验: 检查传入的参数
promises
是否是可迭代对象。如果不是,则 reject 一个TypeError
。 - 转换为数组: 将传入的
promises
转换为数组,方便后续处理。 - 空数组处理: 如果传入的是空数组,则返回一个永远 pending 的 Promise,符合规范.
- 循环遍历: 遍历 Promise 数组,对每个 Promise 进行处理。
- Promise.resolve包装: 使用
Promise.resolve(promise)
将数组中的每一项都包装成 Promise 对象。 - 成功回调: 只要有一个 Promise 成功 fulfilled,就立即 resolve 整个 Promise,并将它的 fulfillment 值传递给 resolve 函数。
- 失败回调: 只要有一个 Promise 被 rejected,就立即 reject 整个 Promise,并将它的 reason 传递给 reject 函数。
测试一下:
const promise6 = new Promise((resolve) => setTimeout(() => resolve(6), 200));
const promise7 = new Promise((resolve) => setTimeout(() => resolve(7), 100));
myPromiseRace([promise6, promise7])
.then((value) => {
console.log('Success:', value); // 输出: Success: 7
})
.catch((error) => {
console.error('Error:', error);
});
const promise8 = new Promise((resolve, reject) => setTimeout(() => reject('Error 8'), 50));
const promise9 = new Promise((resolve) => setTimeout(() => resolve(9), 100));
myPromiseRace([promise8, promise9])
.then((value) => {
console.log('Success:', value);
})
.catch((error) => {
console.error('Error:', error); // 输出: Error: Error 8
});
myPromiseRace([])
.then((value) => {
console.log('Success:', value);
})
.catch((error) => {
console.error('Error:', error);
});
总结:Promise.all
vs. Promise.race
特性 | Promise.all |
Promise.race |
---|---|---|
目标 | 等待所有 Promise 完成 | 等待第一个 Promise 完成 |
成功条件 | 所有 Promise 都 fulfilled | 任何一个 Promise fulfilled |
失败条件 | 任何一个 Promise 被 rejected | 任何一个 Promise 被 rejected |
返回值(成功) | 包含所有 fulfillment 值的数组,顺序与输入一致 | 第一个 fulfilled 的 Promise 的 value |
返回值(失败) | 第一个 rejected 的 Promise 的 reason | 第一个 rejected 的 Promise 的 reason |
彩蛋:Promise.allSettled
ES2020 引入了一个新的 Promise 方法:Promise.allSettled
。它会等待所有的 Promise 都 settled(fulfilled 或 rejected),然后返回一个包含每个 Promise 结果的数组,无论它们是成功还是失败。 这就像一个更宽容的 Promise.all
,它不会因为任何一个 Promise 的失败而导致整个操作失败。
结尾:Promise 的世界,精彩无限
Promise.all
和 Promise.race
是 Promise 中非常重要的两个方法,掌握它们可以让你更有效地处理异步操作,编写更健壮的代码。 希望今天的讲解能够帮助你更好地理解和使用这两个强大的工具。 记住,编程的乐趣在于不断学习和探索,Promise 的世界还有很多精彩等待你去发现! 下次再见!