写在前面
promise是面试中经常被考查的知识点,最近,面试官好像更热衷于考查Promise的相关api的实现,比如promise.all、 race、any、allSettled的实现原理。
我们今天就来逐个拆解一下,做到有备无患!
理论准备
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,状态变为fulfilled,会按照传入顺序返回三者的resolve结果。 - 如果其中有任何一个
reject,则直接返回这个reject结果。
动手实践
- 既然返回是个
promise,那我们先写个promise占位:
Promise.myAll = (args) => {
return new Promise((resolve, reject) => {
resove([])
})
}
- 如果传入promise都成功,则返回三者的resolve结果数组,而且顺序要不变,那么我们遍历执行传入的promise:
定义一个数组作为最后的返回promise的resove结果。每一个promise的执行结果存放在这个数组的对应index位置。
Promise.myAll = (args) => {
return new Promise((resolve, reject) => {
+ const resArr = [];
+ args.forEach((item, index) => {
+ item.then((res) => {
+ resArr[index] = res;
if(index === args.length - 1) resolve(resArr);
+ })
})
})
}
虽然执行会按照遍历顺序,但是返回结果是异步的,也可能最后一个先返回,这时满足了
index === args.length - 1的条件,但是数组其他项都是empty,所以我们需要引入额外的参数来记录已经resolve的promise.
加入count记录已经resolve的promise数量。
Promise.myAll = (args) => {
return new Promise((resolve, reject) => {
const resArr = [];
let count = 0;
args.forEach((item, index) => {
item.then((res) => {
count += 1;
resArr[index] = res;
if(count === args.length) resolve(resArr);
})
})
})
}
- 如果有任何一个
reject,则返回这个reject结果,在执行传入的promise过程中,碰到reject就直接reject,不管其他promise的执行状态。
Promise.myAll = (args) => {
return new Promise((resolve, reject) => {
const resArr = [];
let count = 0;
args.forEach((item, index) => {
item.then((res) => {
count += 1;
resArr[index] = res;
if(count === args.length) resolve(resArr);
}).catch(err => {
reject(err);
})
})
})
}
优化一下,当传入的参数不是可迭代对象时,报错。
如何判断是否是可迭代对象呢?
要成为可迭代对象, 一个对象必须实现
@@iterator方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为@@iterator的属性,可通过常量Symbol.iterator访问该属性
ok, 有了Mdn的这条理论,我们很容易实现:
Promise.myAll = (args) => {
return new Promise((resolve, reject) => {
if(!args[Symbol.iterator]) reject(new Error('arguments is not interator!'))
const resArr = [];
let count = 0;
args.forEach((item, index) => {
item.then((res) => {
count += 1;
resArr[index] = res;
if(count === args.length) resolve(resArr);
}).catch(err => {
reject(err);
})
})
})
}
代码测试
我们准备三个简单的promise来验证我们的结果:
const p1 = new Promise((res,rej)=>{ setTimeout(()=>{ res('promise1 成功返回!') },1000) })
const p2 = new Promise((res,rej)=>{ setTimeout(()=>{ res('promise2 成功返回!') },2000) })
const p3 = new Promise((res,rej)=>{ setTimeout(()=>{ rej('promise3 reject!') },3000) })
我们先看看ES6的promise.all的执行结果:
Promise.all([p1, p2]);
Promise.all([p1, p2, p3]);
我们代码的结果:
传入两个成功的promise返回ok,增加一个失败的也是ok的。
我们再来测试一下非数组传入的情况:
nice!!!看上去还是挺完美的~
写在最后
更进一步的话对数组的每一项是否是promise对象也加一层判断,或者用Promise.resolve包裹转换成promise对象。
这里就不在扩展了,提供一个思路,感觉这个判断方法还蛮有用的,记录一下。
判断是否是promise对象,可以参考vue-next源码的shared模块里的实现方案: [github.com/vuejs/vue-n…link.zhihu.com/?target=htt…
Promise对象: 有.then和catch方法且都是function的对象
const isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};