最近面试遇到的问题,面试官让手写一个promise.all,觉得这个问题值得思考,所以在这里记录下来。
了解promise.all
我们要手写promise.all方法,首先我们需要想知道这个方法做了什么事情,请看下面这段代码。
// 第一个promise实例
const p1 = new Promise((res, rej) => { res(1) })
// 第二个promise实例const p2 = new Promise((res, rej) => { res(2) })
// promise.all()
promise.all(p1, p2).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
复制代码
先说结果,上述的代码输出的结果是 “[1, 2]”。
从代码中我们可以看到,all方法接收的参数是promise实例,而all方法本身的定义就是当接收到的所有的实例全部执行完成并且返回全部都是res时,执行后面的then,如果有其中的一个实例执行到了rej,那么就会执行到catch。
then里接收到的是一个数组,数组的结果是有顺序的,顺序就是传入的实例执行结果的顺序,所以这里的结果是“[1, 2]“。
对all方法了解到这里,我们就已经知道all的功能和返回的结果了,所以下面就进行书写。
实现 myAll
首先,我们知道all方法后面调用了then函数,所以我们可以知道myAll函数返回的应该是promise实例。
function myAll() {
return new Promise((resolve, reject) => {})
}
复制代码
接下来解决参数问题,已知的是接收到的参数是promise实例,但是却不知道具体需要接收多少个参数,所以这里可以用 auguments接收,也可以结合es6的扩展运算符(…)来实现。
function myAll(...p) {
return new Promise((resolve, reject) => {})
}
复制代码
参数接受的问题解决了,接下来就可以利用循环去执行每一个实例了。
function myAll(...p) {
return new Promise((resolve, reject) => {
for(let i = 0; i < p.length; i++) {
p[i].then(res => {
//每个实例返回的结果
}).catch(err => {
reject(err)
})
}
})
}
复制代码
下一个问题,我们已经知道,myAll函数需要监听到所有promise实例执行结果的,那么我们怎么确定执行完成了多少个promise实例呢?没错,我们可以用一个变量进行计数,变量初始为0,每个promise执行完成都使变量值+1,当变量值和实例的长度相同时,我们就可以断定全部的实例都已经执行完成了。
如果其中一个实例执行到catch中呢?那这个时候,我们就不需要改变这个变量的值了,而是直接通过myAll里面的reject将错误返回出去就可以了。
function myAll(...p) {
return new Promise((resolve, reject) => {
let index = 0
for(let i = 0; i < p.length; i++) {
p[i].then(res => {
//每个实例返回的结果
index ++
if(index === p.length) {
// 所有实例执行完成,可以返回结果了
}
}).catch(err => {
reject(err)
})
}
})
}
复制代码
最后一个问题,前面我们知道all方法后面的then接收的是一个数组,并且是一个有顺序的数组,那么这个该怎么实现呢?我们可以先构建一个和实例长度相同的数据,然后利用数组的下标去按照顺序存储结果,下标正好可以和for循环的i对应上。
注意:for循环里定义i如果用的var,要注意变量提升问题
/*
最终程序
*/
function myAll(...p) {
return new Promise((resolve, reject) => {
let index = 0
const res = new Array(p.length)
for(let i = 0; i < p.length; i++) {
p[i].then(res => {
res[i] = res
index ++
if(index === p.length) {
resolve(res)
}
}).catch(err => {
reject(err)
})
}
})
}
复制代码
谢谢各位看官观看。