一,前言
上一篇,主要实现了 Promise 两个实例 API(原型方法):Promise.prototype.catch
和 Promise.prototype.finally
,主要涉及以下几个点:
Promise.prototype.catch
功能测试、原理分析、源码实现;Promise.prototype.finally
功能测试、原理分析、源码实现;
本篇,继续实现 Promise 的核心静态 API(类方法):Promise.all
;
备注:Promise.all 是 Promise 中最复杂的方法;也是日常开发中的高频 API 和 面试考察点;
二,Promise.all 简介
1,API 介绍
Promise.all
功能:
- 批量执行 Promise,返回一个 promise 实例;
- 全部成功才算成功,返回全部执行结果;
- 有一个失败就算失败,返回第一个失败结果;
2,原生 Promise.all 功能测试
- 成功示例:
Promise.all([new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)
}, 1000);
}), 1]).then(data => {
console.log("then", data)
}).catch(err => {
console.log('catch', err)
})
// 执行结果:then [ 200, 1 ]
备注:
- 数字 1 不是 promise,直接被放入结果数组中;
- 执行结果在结果数组中的顺序与执行顺序一致;
- 失败示例:
Promise.all([new Promise((resolve, reject) => {
setTimeout(() => { // 1 秒后成功
resolve(200)
}, 1000);
}), new Promise((resolve, reject) => {
setTimeout(() => { // 3 秒后失败
reject(300)
}, 3000);
}), new Promise((resolve, reject) => {
setTimeout(() => { // 2 秒后失败
reject(400)
}, 2000);
}), 1]).then(data => {
console.log("then", data)
}).catch(err => {
console.log('catch', err)
})
// 执行结果:catch 400
备注:
- 第一个 promise 会先执行完,但由于第三个 promise 先失败了,所以 Promise.all 会进入 catch 并返回第一个失败的结果;
三,Promise.all 实现
1,原理分析
Promise.all
:入参是一个 promise 集合;返回一个 Promise 实例;- 所有 promise 的 resolve 回调结果都会被放入到一个数组;
- 所有 promise 都执行成功且调用 resolve 后,返回的 Promise 才调用 resolve 成功,并返回全部执行结果;
- 任何一个 promise 执行调用 reject 或抛出错误,返回的 Promise 就会调用 reject 失败,并返回第一个失败结果;
2,代码实现
Promise 中创建静态方法 all:
// Promise.all 处理 promise 集合
static all (promises) {
// Promise.all 返回一个 Promise
return new Promise((resolve, reject)=>{
// 创建集合 result,用于顺序存放 Promise 执行结果
// 全部执行成功,调用 resolve(result),返回全部执行结果
let result = [];
// 遍历执行 每一个 promises
for(let i = 0;i < promises.length; i++){
let p = promises[i];
// promise 类型,promise.then
if(p && typeof p.then ==='function'){
p.then((data)=>{
// todo 执行结果放入 result
}, reject) // 任何一个 promise 失败,直接失败
// 非 promise 类型
}else{
// todo
}
}
})
}
还有以下关键问题需要解决:
问题 1:如何按照执行顺序存放异步操作的返回结果?
- 按照异步操作执行顺序,放入 result 数字下标的对应位置;
问题 2:如何判定集合内的 promise 已经全部执行完成?
- 如果使用 result.length,后面异步操作如果先返回放入数组,判断 length 就不准了;
- 可以使用计数器,每次 promise 成功 index++,直至
index === promises.length
;
最终实现如下:
static all (promises) {
return new Promise((resolve, reject)=>{
let result = [];
let times = 0;
// 将成功结果放入数组中对应的位置
const processSuccess = (index, val)=>{
result[index] = val;
if(++times === promises.length){
resolve(result); // 全部执行成功,返回 result
}
}
// 遍历处理集合中的每一个 promise
for(let i = 0;i < promises.length; i++){
let p = promises[i];
if(p && typeof p.then ==='function'){
// 调用这个p的 then 方法
p.then((data)=>{
// 按照执行顺序存放执行结果
processSuccess(i, data)
}, reject);
}else{
// 普通值,直接按照执行顺序放入数组对应位置
processSuccess(i, p)
}
}
})
}
3,功能测试
// 使用自己实现的 Promise.all
Promise.all([new Promise((resolve, reject) => {
setTimeout(() => {
console.log("index = 1; timeout = 1000; resolve(200)")
resolve(200)
}, 1000);
}), new Promise((resolve, reject) => {
setTimeout(() => {
console.log("index = 2; timeout = 3000; reject(300)")
reject(300)
}, 3000);
}), new Promise((resolve, reject) => {
setTimeout(() => {
console.log("index = 3; timeout = 2000; reject(400)")
reject(400)
}, 2000);
}), 1]).then(data => {
console.log("then", data)
}).catch(err => {
console.log('catch', err)
})
// 输出结果
// index = 1; timeout = 1000; resolve(200)
// index = 3; timeout = 2000; reject(400)
// catch 400
// index = 2; timeout = 3000; reject(300)
执行情况分析:
- Promise.all 传入待执行的 promise 集合,内部遍历执行 then 方法,普通值 1 被直接放入 result[3] = 1;(注意:如果用 result.length 判定全部完成,此时的length已经为4,不准)
- 1 秒后,第一个 promise 执行成功,result[0] = 200;
- 2 秒后,第三个 promise 执行失败,执行 reject(300) 进入 then 失败处理,就会调用 Promise.all 内部 new Promise 的 reject,导致 Promise.all 返回一个失败的 promise 对象结果为 300;(注意:此时第二个 promise 已经执行但未执行完成,Promise.all 的失败不会影响它的继续执行,也无法让他停止);
- 3 秒后,第二个 promise 执行失败,执行 reject(400),但由于 Promise.all 已经失败,所以内部创建的 new Promise 实例已经是失败态,不能继续被改变状态;
自己实现的 Promise.all 与原生 Promise.all 表现一致;
四,结尾
本篇,主要实现 Promise 的核心静态 API(类方法):Promise.all
,主要涉及以下几个点:
- 测试原生 Promise.all 的使用;
- Promise.all 的功能与特性分析;
- Promise.all 的源码实现、执行分析、功能测试;
下一篇,继续 Promise 静态 API:Promise.race;
维护记录
- 20211105
- 添加 promise.all 源码实现和执行分析