【手写 Promise 源码】第十一篇 - Promise.all 的实现

957 阅读4分钟

一,前言

上一篇,主要实现了 Promise 两个实例 API(原型方法):Promise.prototype.catchPromise.prototype.finally,主要涉及以下几个点:

  • Promise.prototype.catch 功能测试、原理分析、源码实现;
  • Promise.prototype.finally 功能测试、原理分析、源码实现;

本篇,继续实现 Promise 的核心静态 API(类方法):Promise.all;

备注:Promise.all 是 Promise 中最复杂的方法;也是日常开发中的高频 API 和 面试考察点;


二,Promise.all 简介

1,API 介绍

MDN 参考资料

image.png

Promise.all功能:

  • 批量执行 Promise,返回一个 promise 实例;
  • 全部成功才算成功,返回全部执行结果;
  • 有一个失败就算失败,返回第一个失败结果;

2,原生 Promise.all 功能测试

  1. 成功示例:
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,直接被放入结果数组中;
  • 执行结果在结果数组中的顺序与执行顺序一致;
  1. 失败示例:
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 源码实现和执行分析