深度解析Promise.all的核心功能并手写实现

在之前的文章中,已经手写实现了Promise的核心功能,包括resolverejectthenPromise还有一些拓展方法,比如Promise.all

在手写实现一些原生提供的方法时,第一步要做的事情就是先了解这个方法的使用过程和基本原理。

所以我们先了解一下Promise.all做的事情。

原生Promise.all的使用

以下是MDN文档的简述:

Promise.all() 方法接收一个promiseiterable类型(注:ArrayMapSet都属于ES6iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promiseresolve回调的结果是一个数组。这个Promiseresolve回调执行是在所有输入的promiseresolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promisereject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。

简单来说就是批量化处理promise实例以一个数组或者类数组的方式存储,当所有的promiseresolve都执行完成,或者存在任意一个promisereject被执行,都立即返回。

要么全成功,一起返回,只要有一个失败,就立即返回当前失败的内容。

下面我们简单用一下。

let promise1 = new Promise((resolve, reject) => {
  resolve(1);
})
let promise2 = new Promise((resolve, reject) => {
  resolve(2);
})
let promise3 = new Promise((resolve, reject) => {
  resolve(3);
})

Promise.all([promise1, promise2, promise3])
.then(res => {
  console.log(res);
}, err => {
  console.log(err);
})

在这里插入图片描述

输出的结果如上所示。

我们知道原生Promise.all的基本原理和使用方法之后,试着来自己实现以下。

手写实现Promise.all

Promise.all = function(promises) {
  let results = [];
  let length = promises.length;
  let promiseCount = 0;
  return new Promise((resolve, reject) => {
    for (let i of promise1) {
      Promise.resolve(i).then(res => {
        results.push(res);
        promiseCount ++;
        
        if (promiseCount === length) {
          resolve(results);
        }
      }, err => {
        reject(err);
      })
    }
  })
}

简单解释一下上面的代码,首先函数接收一个参数promises,代表保存了很多promise实例的数组或类数组。

results是用来存储每个promise实例执行之后的结果。因为最后要以数组的方式将所有promise的执行结果返回。

promiseCount是记录promise的执行次数,当所有的参数中所有的promise实例都执行完成,就将results返回。

接下来,return一个新的promise实例。在官方文档中有说明,Promise.all方法最终会返回一个Promise实例。

在构造函数中的处理,循环接收到的 promises参数,挨个执行,需要注意的一点是,避免使用者传入非Promise类型的元素,所以在遍历的时候将每个元素都用Promise.resolve包裹一下。

接下来的操作就是将当前遍历的promise实例的resolve执行结果添加到results数组中,promiseCount ++,代表当前实例执行完了。如果promiseCount === length就代表,所有promise元素都执行完了。返回存储所有promise执行结果的results数组即可。

如果有任意一个promise元素执行的是reject方法,立即结束当前循环,执行reject方法,返回报错信息。

其实以上源码可以实现基本功能,但是还有点瑕疵。在给往results数组中push当前遍历的promise执行结果时,其实不准确。因为promise实例中处理的可能是异步事件,大部分也都是异步事件。直接push并不准确。

比如传入的promiseresolve输出是[1, 2, 3]; 如果第一个promise实例中存在异步事件,比如加了一个定时器,按照当前的写法,直接 push的话Promise.all返回的一定是[2, 3, 1]; 这其实是不对的。应该按照顺序返回。

用原生的Promise.all试一下。

let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  })
})
let promise2 = new Promise((resolve, reject) => {
  resolve(2);
})
let promise3 = new Promise((resolve, reject) => {
  resolve(3);
})

Promise.all([promise1, promise2, promise3])
.then(res => {
  console.log(res);
}, err => {
  console.log(err);
})

在这里插入图片描述
与猜测一致。无论返回的携带有promise执行结果的数组中的顺序应该与传入时的顺序一致。

优化版的源码如下:

Promise.all = function(promises) {
  let results = [];
  let length = promises.length;
  let promiseCount = 0;
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(res => {
        results[i] = res;
        promiseCount ++;
        
        if (promiseCount === length) {
          resolve(results);
        }
      }, err => {
        reject(err);
      })
    }
  })
}

解决方案其实就是,通过当前遍历传入参数的索引来作为results的索引,往里面添加。

这样就一定是按输入的顺序返回。

欢迎大家关注我的公众号,有很多关于前端的内容哦
QQ:505417246
WX:18331092918
公众号:Code程序人生
B站账号:LuckyRay123
个人博客:http://rayblog.ltd/

你可能感兴趣的:(前端,面试,js,javascript,前端,开发语言)