promise.all在日常项目里经常会碰到,今天咋们手写下promise.all这个方法,并且探究下其中的细节。
先来几个demo
const demo1 = new Promise((resolve, reject) =>{
setTimeout(() => {
resolve(1)
}, 1000)
})
const demo2 = new Promise((resolve, reject) =>{
setTimeout(() => {
resolve(2)
}, 2000)
})
const demo3 = new Promise((resolve, reject) =>{
setTimeout(() => {
resolve(3)
}, 3000)
})
const demo4 = new Promise((resolve, reject) => {
setTimeout(() =>{
reslove(4)
}, 4000)
})
开始进行
第一步我们首先应该想到promise.all是可以进行链式调用的,那么内部一定返回的是一个promise对象。
function myPromiseAll(promiseArr) {
return new Promise(resolve, reject => {
···
})
}
第二步就应该对参数进行判断。任何方法最开始都应该想到,避免后期参数类型引起的其他错误。
function myPromiseAll(promiseArr) {
return new Promise(resolve, reject => {
if(Array.isArray(promiseArr)) {
······
}else {
throw new Error('请传入数组作为参数')
}
})
}
第三步我们对数组进行遍历,这个时候应该想到会不会数组里面的元素不是一个promise对象呢?可能有人就会进行if
判断,可以使用instanceof 或者万能的Object.prototype.toString.call(),但其实有更简单的办法,就是直接使用Promise.resolve()包裹元素就会自动将非promise元素变成一个promise对象。
function myPromiseAll(promiseArr) {
return new Promise(resolve, reject => {
if(Array.isArray(promiseArr)) {
const len = promiseArr.length
for(let i = 0; i < len; i ++) {
Promise.resolve(promiseArr[i]).then(res => {
······
}).catch(err => {
reject(err)
})
}
}else {
throw new Error('请传入数组作为参数')
}
})
}
第四步,根据返回的结果是一个数组我们就应该想到需要创建一个数组进行结果保存。这个时候就会出现最容易犯错的地方,那就是数组顺序
,我们会发现官方的promise.all返回的数组元素里面的顺序是传入的promise数组参数的顺序。直接push可能会因为执行时间的不同从而push进结果数组的时机不同,这个时候就需要使用数组下标的方式来进行赋值,最后在判断什么时候结束判断,那就是结果数组长度等于参数的时候。
function myPromiseAll(promiseArr) {
return new Promise((resolve, reject) => {
if(Array.isArray(promiseArr)) {
const len = promiseArr.length
let resArr = []
for(let i = 0; i < len; i ++) {
Promise.resolve(promiseArr[i]).then(res => {
resArr[i] = res
if(resArr.length === len) {
resolve(resArr)
}
}).catch(err => {
reject(err)
})
}
}else {
throw new Error('请传入数组作为参数')
}
})
}
第五步,大家觉得这样就结束了?如果大家测试就会发现,上面的promise.all方法执行myPromiseAll([demo1, demo2, demo3, demo4])
那是没有丝毫问题的,但是这个参数顺序是根据递增的顺序变化的,也就是说他们执行的顺序和参数顺序是一致的,如果我们置换顺序,比如demo4, demo1, demo2, demo3
就会发现该数组的第一个结果竟然是空(empty
),也就是说返回的结果是一个稀缺数组。那是因为第一个参数定时时间最长,所以会在最后执行,当执行到demo3的时候,i
就已经等于3
了,而数组使用下标进行赋值时,会改变数组的长度,尽管有时候下标的值是空(empty),所以就执行了resolve
,但这是demo4
并没有执行,所以会造成结果的缺失。这个时候我们就需要一个中间数字来记录我们执行了几个参数。
function myPromiseAll(promiseArr) {
return new Promise((resolve, reject) => {
if(Array.isArray(promiseArr)) {
const len = promiseArr.length
let resArr = []
let num = 0
for(let i = 0; i < len; i ++) {
Promise.resolve(promiseArr[i]).then(res => {
num ++
resArr[i] = res
if(num === len) {
resolve(resArr)
}
}).catch(err => {
reject(err)
})
}
}else {
throw new Error('请传入数组作为参数')
}
})
}
最后大功告成!
注意点总结:
- 注意参数的类型
- 注意参数数组里面的元素可以不为promise对象
- 注意成功是返回的结果顺序