在之前的文章中,已经手写实现了Promise
的核心功能,包括resolve
,reject
,then
。Promise
还有一些拓展方法,比如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 = 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
的索引,往里面添加。
这样就一定是按输入的顺序返回。
欢迎大家关注我的公众号,有很多关于前端的内容哦
QQ:505417246
WX:18331092918
公众号:Code程序人生
B站账号:LuckyRay123
个人博客:http://rayblog.ltd/