携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
前言
手写Promise相关经常是各大公司手撕代码环节会被问到的问题,上一篇忌惮了许久的手写Promise终于被我AC了实现了一个丐版的Promise,对于校招生应付面试应该没问题了,感兴趣可以去看看,但是要完全遵循Promise/A+规范还有一段距离。这一篇,我们来实现又一个面试高频考点手写Promise.all
Promise.all是什么
Promise.all是Promise类的一个静态方法,先来看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.all可以接收一个可迭代类型,比如数组、Map、Set,拿数组来举例,它允许我们按照异步代码调用的顺序得到执行结果,并将执行结果放入一个数组中,异步代码调用的顺序就是我们传入数组的顺序。
我们来看一个🌰:
function p1() {
return new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('p1')
}, 2000)
})
}
function p2() {
return new Promise(function (resolve, reject) {
resolve('p2')
})
}
Promise.all(['a', 'b', p1(), p2(), 'c']).then((result) => {
console.log(result)
})
p1是一个定时任务,p2是一个同步任务,返回结果如下:
[ 'a', 'b', 'p1', 'p2', 'c' ]
由此可以解释按照调用的顺序执行
如果数组里传入的Promise变成reject状态呢?MDN上如是说:Promise.all 在任意一个传入的 promise 失败时返回失败。例如,如果你传入的 promise中,有四个 promise 在一定的时间之后调用成功函数,有一个立即调用失败函数,那么 Promise.all 将立即变为失败。
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
reject('reject');
});
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
//From console:
//"reject"
//You can also use .catch
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}).catch(reason => {
console.log(reason)
});
手撕环节
由于我们只需要实现Promise.all这个静态方法,因此我们让MyPromise继承ES6的Promise,然后我们重载all方法即可,有了上面的铺垫,实现代码就比较简单了👇
class MyPromise extends Promise {
static all(array) {
let res = [] //记录结果的数组
function addData(key, value) {
res[key] = value
}
return new Promise((resolve, reject) => {
for (let i = 0; i < array.length; i++) {
let cur = array[i]
if (cur instanceof Promise) {
//promise对象
cur.then(value => addData(i, value), reason => reject(reason))
} else {
//普通值
addData(i, array[i])
}
}
resolve(res)
})
}
}
这样我们的Promise.all就实现完成。。。。。。。了吗?我们测试一下看看:
function p1() {
return new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('p1')
}, 2000)
})
}
function p2() {
return new Promise(function (resolve, reject) {
resolve('p2')
})
}
MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(res => console.log(res))
输出结果如下:
[ 'a', 'b', <1 empty item>, 'p2', 'c' ]
咦,中间这个<1 empty item>是什么?
因为我们上面用了一个for循环,for循环是一瞬间就执行完成了,而我们在执行for循环的过程中是有定时器异步任务的,当我们最后调用resolve的时候,异步任务还未执行完成,所以在我们的结果当中就出现了empty空值。
我们来优化一下代码,优化的思路就是我们不要在for循环结束后就把结果resolve出去,可以把结果延迟到所有的异步任务都执行完成后再resolve出去,如何延迟呢?可以定一个index,每次执行addData时index++,当index===array.length时,再调用resolve方法。优化后的代码如下:
class MyPromise extends Promise {
static all(array) {
let res = []
let index = 0
return new Promise((resolve, reject) => {
//addData移到了Promise里面,因为要调用resolve方法
function addData(key, value) {
res[key] = value
index++
if (index === array.length) {
resolve(res)
}
}
for (let i = 0; i < array.length; i++) {
let cur = array[i]
if (cur instanceof Promise) {
//promise对象
cur.then(value => addData(i, value), reason => reject(reason))
} else {
//普通值
addData(i, array[i])
}
}
})
}
}
我们现在再来控制台看一下结果,可以看到延迟两秒后输出
[ 'a', 'b', 'p1', 'p2', 'c' ]
至此,一份手写Promise.all就新鲜出炉了。