如何实现一个Promise.all
  # 0.前言
Promise系列先写起来,这道题目来自B站up主山月被头条面试出的一道考题,相比于传统的手写Promise.all()加了很多限制条件,题目如下:
满足
Promise.all的所有规则传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
除了传统的条件之外,仍需满足以下两个条件:
- 传入一个
Iterable的对象,不一定是数组。 - 要求并行执行。
 
- 传入一个
 
# 1.测试代码:
await Promise.all([1, Promise.resolve(2)]);
//-> [1, 2]
await Promise.all([1, Promise.reject(2)]);
//-> Throw Error: 2
 2
3
4
5
测试函数的并行执行,需要实现一个sleep函数
// 输入时间,返回Promise对象
// 一开始是pending状态,过了seconds秒后,变为resolve状态
const sleep = (seconds) =>
  new Promise((resolve) => setTimeout(() => resolve(seconds), seconds));
 2
3
4
使用以下示例代码测试:
pAll([1, 2, 3]).then((o) => console.log(o));
pAll([sleep(3000), sleep(2000), sleep(1000)]).then((o) => console.log(o));
pAll([sleep(3000), sleep(2000), sleep(1000), Promise.reject(10000)])
  .then((o) => console.log(o))
  .catch((e) => console.log(e, "<- Error"));
 2
3
4
5
# 2.答案
# 山月版本:
const pAll = function(_promisesArr){
 	const result = [];
  let count = 0
  return new Promise((resolve,reject)=>{
   // + 第一步: 将 Iterable => Array
    const promiseArray = Array.from(_promises)
    // + 使用 for 循环去遍历数组 | 当然也可以使用 forEach
    for(let i = 0;i<promiseArr.length;i++){
        // 注意直接使用 promise[i].then 是错误的,因为还有考虑返回的结果不是Promise的情况
        Promise.resolve(promise[i]).then(val=>{
        count++
        result[i] = value
        if(count === promiseArr.length){
          resolve(result)
        }
      },err =>{
        	reject(err)
      })
    }
  })
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 常规版:
Promise.all = function(promiseArr) {
    let index = 0, result = []
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                index++
                result[i] = val
                if (index === promiseArr.length) {
                    resolve(result)
                }
            }, err => {
                reject(err)
            })
        })
    })
}
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.需要注意的知识点
使用
for...of是最值得推荐的去遍历Iterable对象的方法。观察一个对象是否
Iterable,只需要打印出来,是否具有[Symbol.iterator]属性即可,具体内容详见:阮一峰Iterator章节 (opens new window),讲的非常清楚。限制条件二,要求遍历是并行的,这一点在之前我写的《深入理解forEach与forof》,以及《forEach/for...in/of》对于各种类型的数据该用什么样的遍历方式,以及对应的遍历的特性已经讲解的很清楚了。
在对
promiseArr进行循环时,一直写的是下面第一种写法,特此注释加以记忆。promise[i].then(只能处理Promise对象)Promise.resolve(promise[i]).then(可以处理处理非Promise对象)
// 第一种方法只能通过: Promise.all([Promise.resolve(1),Promise.resolve(2)]) // 而无法通过 非Promise对象 Promise.all([1,2,3])1
2
3
4进一步解释:为啥在外面包一层
Promise.resolve就可以了呢?因为:
Promise在实现时,就已经考虑到对于这种纯结果(官网的表述是:if x is not an object or function)的处理,详见:PromiseA+ (opens new window)的2.3.4规范。ps.其实
Promise在实现这块时也不难,直接return new Promise(xxxx)就可以解决了,难的在于2.3.3如果x是一个thenable的对象,这块始终是云里雾里。