在之前的文章中,已经手写实现了Promise的核心功能,包括resolve,reject,then。Promise还有一些拓展方法,比如Promise.all
在手写实现一些原生提供的方法时,第一步要做的事情就是先了解这个方法的使用过程和基本原理。
所以我们先了解一下Promise.all做的事情。
原生Promise.all的使用
以下是MDN文档的简述:
Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
简单来说就是批量化处理promise实例以一个数组或者类数组的方式存储,当所有的promise的resolve都执行完成,或者存在任意一个promise的reject被执行,都立即返回。
要么全成功,一起返回,只要有一个失败,就立即返回当前失败的内容。
下面我们简单用一下。
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并不准确。
比如传入的promise的resolve输出是[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的索引,往里面添加。
这样就一定是按输入的顺序返回。
欢迎大家关注我的公众号,有很多关于前端的内容哦
公众号:Code程序人生
B站账号:LuckyRay123
个人博客:http://rayblog.ltd/










