前言
Promise 是 ES6 提供的异步解决方案,而 Promise.all 是处理多个并发异步任务的核心方法。
面试中经常被问到:
"请手写一个 Promise.all,并说明它和 Promise.race 的区别"
今天我们从 Promise 的基础出发,结合原型链知识,彻底搞懂 Promise.all 的原理和实现。
一、Promise 基础回顾
1.1 Promise 的三种状态
// 1.html
const p = new Promise((resolve, reject) => {
console.log(111); // 同步执行
setTimeout(() => {
console.log(333);
reject('失败1'); // 状态变为 rejected
}, 1000);
});
console.log(222);
console.log(p);
p.then((data) => {
console.log(data);
}).catch(err => {
console.log(err);
}).finally(() => {
console.log('finally');
});
逐行解析:
const p = new Promise((resolve, reject) => {
new Promise创建 Promise 实例- 构造函数接收一个 executor 函数,立即执行
- executor 接收两个参数:
resolve(成功回调)和reject(失败回调)
console.log(111);
- executor 是同步执行的
- 所以
111会立即输出
setTimeout(() => {
console.log(333);
reject('失败1');
}, 1000);
setTimeout是异步任务,1 秒后执行reject('失败1')将 Promise 状态从pending改为rejected- 传递的参数会传给
.catch的回调
console.log(222);
console.log(p);
- 同步代码继续执行,输出
222 p是 Promise 实例,此时状态是pending
执行顺序:
111 → 222 → [Promise 对象] → (1秒后) 333 → 失败1 → finally
Promise 状态流转:
| 状态 | 说明 | 可改变为 |
|---|---|---|
pending | 初始状态,进行中 | fulfilled 或 rejected |
fulfilled | 操作成功 | 不可改变 |
rejected | 操作失败 | 不可改变 |
1.2 Promise 的原型链结构
console.log(p.__proto__ == Promise.prototype); // true
console.log(p.__proto__);
逐行解析:
p.__proto__ == Promise.prototype
- Promise 实例的
__proto__指向Promise.prototype - 这是 JS 原型链的核心机制
console.log(p.__proto__);
- 输出 Promise 原型对象,包含:
then方法catch方法finally方法constructor指向 Promise 构造函数
原型链关系:
Promise 实例 (p)
↓ __proto__
Promise.prototype
↓ __proto__
Object.prototype
↓ __proto__
null
实例方法 vs 静态方法:
| 类型 | 方法 | 挂载位置 | 调用方式 |
|---|---|---|---|
| 实例方法 | then、catch、finally | Promise.prototype | p.then() |
| 静态方法 | all、race、resolve、reject | Promise 构造函数 | Promise.all() |
二、Promise.all 的使用
2.1 基本用法
const p1 = new Promise((resolve) => {
setTimeout(() => resolve('结果1'), 1000);
});
const p2 = new Promise((resolve) => {
setTimeout(() => resolve('结果2'), 2000);
});
const p3 = new Promise((resolve) => {
setTimeout(() => resolve('结果3'), 3000);
});
Promise.all([p1, p2, p3]).then((results) => {
console.log(results); // ['结果1', '结果2', '结果3']
});
特点:
- 接收一个可迭代对象(数组、Set 等)
- 所有 Promise 都成功时,返回结果数组
- 结果顺序与入参顺序一致
- 任一 Promise 失败,立即 reject
2.2 实际应用场景
// 并发请求多个接口
const getUser = () => fetch('/api/user').then(res => res.json());
const getPosts = () => fetch('/api/posts').then(res => res.json());
const getComments = () => fetch('/api/comments').then(res => res.json());
Promise.all([getUser(), getPosts(), getComments()])
.then(([user, posts, comments]) => {
console.log('用户信息:', user);
console.log('文章列表:', posts);
console.log('评论列表:', comments);
})
.catch(err => {
console.error('请求失败:', err);
});
优势:
- 三个请求并发执行,不是串行
- 总耗时取决于最慢的那个请求
- 代码简洁,易于维护
三、手写 Promise.all
3.1 基础实现
function myPromiseAll(promises) {
// 1. 返回一个新的 Promise
return new Promise((resolve, reject) => {
// 2. 参数校验
if (!Array.isArray(promises)) {
return reject(new TypeError('promises must be an array'));
}
// 3. 空数组处理
if (promises.length === 0) {
return resolve([]);
}
// 4. 结果数组和计数器
const results = [];
let completedCount = 0;
// 5. 遍历每个 Promise
promises.forEach((promise, index) => {
// 6. 包装为 Promise(处理非 Promise 值)
Promise.resolve(promise).then(
(value) => {
// 7. 成功时保存结果
results[index] = value;
completedCount++;
// 8. 所有 Promise 都完成
if (completedCount === promises.length) {
resolve(results);
}
},
(error) => {
// 9. 任一失败,立即 reject
reject(error);
}
);
});
});
}
// 测试
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
myPromiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
逐行解析:
return new Promise((resolve, reject) => {
Promise.all返回一个新的 Promise- 这个 Promise 的状态取决于所有传入的 Promise
if (!Array.isArray(promises)) {
return reject(new TypeError('promises must be an array'));
}
- 参数必须是数组(或可迭代对象)
- 原生
Promise.all支持所有可迭代对象,这里简化为数组
if (promises.length === 0) {
return resolve([]);
}
- 空数组直接返回空数组
- 这是边界情况处理
const results = [];
let completedCount = 0;
results:保存每个 Promise 的结果completedCount:记录已完成的 Promise 数量- 为什么用计数器? 因为 Promise 是异步的,需要等待所有完成
promises.forEach((promise, index) => {
- 遍历每个 Promise
index用于保持结果顺序
Promise.resolve(promise).then(
Promise.resolve将值包装为 Promise- 如果已经是 Promise,直接返回
- 如果是普通值,包装成 resolved 状态的 Promise
(value) => {
results[index] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
}
- 成功回调:保存结果到对应位置
- 计数器加 1
- 当所有 Promise 都完成时,resolve 结果数组
- 为什么用
results[index] = value? 保证结果顺序与入参一致
(error) => {
reject(error);
}
- 失败回调:任一 Promise 失败,立即 reject
- 传递第一个失败的错误信息
3.2 完整实现(支持可迭代对象)
function myPromiseAll(iterable) {
return new Promise((resolve, reject) => {
// 1. 转换为数组
const promises = Array.from(iterable);
// 2. 空数组处理
if (promises.length === 0) {
return resolve([]);
}
// 3. 结果数组和计数器
const results = [];
let completedCount = 0;
// 4. 遍历每个 Promise
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => {
results[index] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
},
(error) => {
reject(error);
}
);
});
});
}
// 测试 Set
const set = new Set([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]);
myPromiseAll(set).then(console.log); // [1, 2, 3]
改进点:
const promises = Array.from(iterable);
- 使用
Array.from将可迭代对象转换为数组 - 支持 Set、Map、字符串等可迭代对象
四、Promise 原型链与静态方法
4.1 原型链关系
// 2.js
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speci = '人类';
let zhen = new Person('郑总', 18);
console.log(zhen.speci); // '人类'
const kong = {
name: '孔子',
hobbies: ['读书', '喝酒']
};
zhen.__proto__ = kong;
console.log(zhen.hobbies, zhen.speci);
// ['读书', '喝酒'] undefined
逐行解析:
Person.prototype.speci = '人类';
- 在
Person.prototype上添加属性 - 所有实例都可以访问
let zhen = new Person('郑总', 18);
console.log(zhen.speci);
zhen自身没有speci属性- 沿着原型链查找到
Person.prototype.speci - 输出
'人类'
zhen.__proto__ = kong;
- 修改
zhen的原型指向kong对象 - 注意: 这会破坏原型链,不推荐
console.log(zhen.hobbies, zhen.speci);
zhen.hobbies:从kong对象获取zhen.speci:原型已改变,找不到,输出undefined
原型链对比:
| 对象 | 原生原型链 | 修改后 |
|---|---|---|
zhen | zhen → Person.prototype → Object.prototype → null | zhen → kong → Object.prototype → null |
4.2 Promise 的静态方法
// Promise.all 是静态方法
console.log(typeof Promise.all); // 'function'
console.log(typeof Promise.prototype.all); // 'undefined'
// Promise 实例不能直接调用 all
const p = Promise.resolve(1);
console.log(typeof p.all); // 'undefined'
为什么 Promise.all 是静态方法?
Promise.all不需要访问实例的状态- 它接收多个 Promise,返回一个新的 Promise
- 与具体实例无关,所以挂在构造函数上
静态方法 vs 实例方法:
// 静态方法:挂在构造函数上
Promise.all([p1, p2, p3]);
Promise.race([p1, p2]);
Promise.resolve(value);
// 实例方法:挂在原型上
p.then(onFulfilled, onRejected);
p.catch(onRejected);
p.finally(onFinally);
五、扩展:Promise.race 和 Promise.allSettled
5.1 Promise.race
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
Promise.resolve(promise).then(
(value) => resolve(value), // 第一个成功的
(error) => reject(error) // 第一个失败的
);
});
});
}
// 测试
const p1 = new Promise((resolve) => {
setTimeout(() => resolve('结果1'), 1000);
});
const p2 = new Promise((resolve) => {
setTimeout(() => resolve('结果2'), 500);
});
myPromiseRace([p1, p2]).then(console.log); // '结果2'(先完成)
特点:
- 返回第一个完成的 Promise 结果
- 无论成功还是失败
- 常用于超时控制
Promise.all vs Promise.race:
| 方法 | 成功条件 | 失败条件 | 返回值 |
|---|---|---|---|
Promise.all | 所有都成功 | 任一失败 | 结果数组 |
Promise.race | 第一个成功 | 第一个失败 | 单个结果 |
5.2 Promise.allSettled
function myPromiseAllSettled(promises) {
return new Promise((resolve) => {
const results = [];
let completedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => {
results[index] = { status: 'fulfilled', value };
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
},
(error) => {
results[index] = { status: 'rejected', reason: error };
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
}
);
});
});
}
// 测试
const p1 = Promise.resolve('成功1');
const p2 = Promise.reject('失败2');
const p3 = Promise.resolve('成功3');
myPromiseAllSettled([p1, p2, p3]).then(console.log);
// [
// { status: 'fulfilled', value: '成功1' },
// { status: 'rejected', reason: '失败2' },
// { status: 'fulfilled', value: '成功3' }
// ]
特点:
- 等待所有 Promise 完成(无论成功失败)
- 返回每个 Promise 的状态和结果
- 不会因某个失败而中断
Promise 方法对比:
| 方法 | 等待条件 | 成功返回 | 失败返回 |
|---|---|---|---|
Promise.all | 所有成功 | 结果数组 | 第一个错误 |
Promise.race | 第一个完成 | 第一个结果 | 第一个错误 |
Promise.allSettled | 所有完成 | 状态数组 | 不会失败 |
Promise.any | 第一个成功 | 第一个结果 | 所有失败(AggregateError) |
5.3 Promise.any
function myPromiseAny(promises) {
return new Promise((resolve, reject) => {
const errors = [];
let rejectedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => resolve(value), // 第一个成功就 resolve
(error) => {
errors[index] = error;
rejectedCount++;
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
}
// 测试
const p1 = Promise.reject('失败1');
const p2 = Promise.reject('失败2');
const p3 = Promise.resolve('成功3');
myPromiseAny([p1, p2, p3]).then(console.log); // '成功3'
特点:
- 返回第一个成功的 Promise
- 所有都失败时,抛出
AggregateError - 与
Promise.race相反:race 返回第一个完成的,any 返回第一个成功的
六、经典面试题
面试题 1:Promise.all 处理空数组
Promise.all([]).then(console.log); // []
为什么返回空数组?
- 空数组没有 Promise 需要等待
- 直接 resolve 空数组
- 这是合理的边界情况
面试题 2:Promise.all 中的非 Promise 值
Promise.all([1, Promise.resolve(2), 3]).then(console.log);
// [1, 2, 3]
为什么能处理普通值?
Promise.resolve将普通值包装为 Promise- 1 和 3 被包装为
Promise.resolve(1)和Promise.resolve(3) - 所有值都被统一处理
面试题 3:Promise.all 的错误处理
const p1 = Promise.resolve(1);
const p2 = Promise.reject('错误2');
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(console.log)
.catch(console.error); // '错误2'
为什么只输出错误 2?
p2失败,Promise.all立即 rejectp3虽然成功,但结果被忽略- 注意:
p3仍然会执行,只是结果不会被处理
面试题 4:如何实现并发限制?
function asyncPool(limit, promises, iteratorFn) {
const results = [];
const executing = [];
for (const item of promises) {
const p = Promise.resolve().then(() => iteratorFn(item));
results.push(p);
if (limit <= promises.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
Promise.race(executing).then(() => {});
}
}
}
return Promise.all(results);
}
// 使用:最多同时执行 2 个任务
asyncPool(2, [1, 2, 3, 4, 5], (item) => {
return new Promise((resolve) => {
setTimeout(() => resolve(item), 1000);
});
});
核心思路:
- 维护一个执行队列
- 当队列长度达到限制时,等待某个任务完成
- 使用
Promise.race实现"等待最快完成的任务"
七、总结
核心要点
- Promise.all 接收可迭代对象,返回新的 Promise
- 所有成功时 resolve 结果数组,任一失败时 reject
- 结果顺序与入参顺序一致
- 使用计数器跟踪已完成的 Promise 数量
Promise.all是静态方法,挂在构造函数上
实现步骤
1. 返回新的 Promise
2. 参数校验(数组/可迭代对象)
3. 空数组处理
4. 遍历每个 Promise
5. 使用 Promise.resolve 包装
6. 成功时保存结果,计数器加 1
7. 所有完成时 resolve
8. 任一失败时 reject
方法对比
| 方法 | 用途 | 成功条件 | 失败条件 |
|---|---|---|---|
Promise.all | 并发请求 | 所有成功 | 任一失败 |
Promise.race | 超时控制 | 第一个完成 | 第一个失败 |
Promise.allSettled | 批量处理 | 所有完成 | 不会失败 |
Promise.any | 竞速成功 | 第一个成功 | 所有失败 |