当前位置: 首页 > Web前端 > JavaScript

因为Promise.all无法兑现,面试就凉了

时间:2023-03-27 11:49:31 JavaScript

前言(?﹏?)有一件真实的事情发生在朋友身上。面试官让他手写一个Promise.all。这位朋友当场表现不佳。他没有写出来。之后问了面试官模糊的评价,基础不够扎实,对原理的了解也很少……当然,整个面试的失败不只是这个问题,肯定还有其他原因。但这给我们敲响了警钟:Promise手写实现和Promise静态方法实现已经是面试中的高频考题了。如果你对他们了解不多,会耽误你10分钟。一起努力,直到他懂了O(∩_∩)O常见面试手写系列黑头鱼最近想搞点事情。希望把前端面试常见的手写题写成一个系列,尽量把其中涉及的知识和原理解释清楚。如果你对本系列熟悉有兴趣,欢迎一起学习,有66+手写题实现!1.点击查看日工题1源地址(目前已经实现了66+手写题)2.掘金栏目Promise.resolve简单回顾Promise.resolve(value)方法返回一个Promise对象resolvedwitha给定的价值。如果该值是一个承诺,那么该承诺将被返回;如果该值是thenable(即使用“then”方法),则返回的promise将“跟随”thenable对象,采用其最终状态;否则,返回的promise将使用该值完成。这是MDN上的解释。下面我们一一来看一下Promise.resolve的最终结果。它仍然是一个Promise,它与Promise.resolve(值)传入的值密切相关。传入的参数可以是Promise实例,那么函数执行的结果最重要的是要理解这里直接返回实例。可以这样理解,Promise的最终状态就是thenable对象输出的值//1.Non-Promiseobjects,非thenable对象Promise.resolve(1).then(console.log)//1//2.Promise对象成功状态constp2=newPromise((resolve)=>resolve(2))Promise.resolve(p2).then(console.log)//2//3.Promise对象失败状态constp3=newPromise((_,reject)=>reject('err3'))Promise.resolve(p3).catch(console.error)//err3//4.thenableobjectconstp4={then(resolve){setTimeout(()=>resolve(4),1000)}}Promise.resolve(p4).then(console.log)//4//5.没有传递任何内容Promise.resolve().then(console.log)//未定义源码实现Promise.myResolve=function(value){//是一个Promise实例,直接返回即可if(value&&typeofvalue==='object'&&(valueinstanceofPromise)){returnvalue}//否则,其他情况用Promise包裹起来returnnewPromise((resolve)=>{resolve(value)})}//测试一下,或者使用例子justnow//1.非Promise对象,不是thenableobjectPromise.myResolve(1).then(console.log)//1//2.Promise对象成功状态constp2=newPromise((resolve)=>resolve(2))Promise.myResolve(p2).then(console.log)//2//3.Promise对象失败状态constp3=newPromise((_,reject)=>reject('err3'))Promise.myResolve(p3).catch(console.error)//err3//4.thenableobjectconstp4={then(resolve){setTimeout(()=>resolve(4),1000)}}Promise.myResolve(p4).then(console.log)//4//5.NothingispassedPromise.myResolve().then(console.log)//undefinedquestion从源码实现来看,没有对thenable对象进行特殊处理!其实不需要在Promise.resolve中处理。真正的处理地方应该在Promise构造函数中。如果你对此感兴趣,我会尽快写出Promise的实现。期待您的阅读。Promise.reject很简短回想一下,Promise.reject()方法返回一个带有拒绝原因的Promise。Promise.reject(newError('fail')).then(()=>console.log('Resolved'),(err)=>console.log('Rejected',err))//输出如下//RejectedError:fail//at:2:16reject的源码实现比较简单,只要返回一个新的Promise,并将结果状态设置为reject即可Promise.myReject=function(value){returnnewPromise((_,reject)=>{reject(value)})}//TestPromise.myReject(newError('fail')).then(()=>console.log('Resolved'),(err)=>console.log('Rejected',err))//RejectedError:fail//at:9:18Promise.all简单回顾一下Promise.all()方法用于包装多个Promiseinstances到一个新的Promise实例中。这种静态方法在面试中应该是最常见的。constp=Promise.all([p1,p2,p3])p的最终状态由p1,p2,p3决定,分为两种情况。(1)只有当p1、p2和p3的状态成立时,p的状态才会成立。此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(2)只要p1、p2、p3其中之一被拒绝,p的状态就变成被拒绝。这时候第一个被拒绝的实例的返回值就会传递给p的回调函数。constp1=承诺。resolve(1)constp2=newPromise((resolve)=>{setTimeout(()=>resolve(2),1000)})constp3=newPromise((resolve)=>{setTimeout(()=>resolve(3),3000)})constp4=Promise.reject('err4')constp5=Promise.reject('err5')//1.所有Promise都成功constp11=Promise.all([p1,p2,p3]).then(console.log)//[1,2,3].catch(console.log)//2.一个Promise失败constp12=Promise.all([p1,p2,p4]).then(console.log).catch(console.log)//err4//3.两次Promise失败,可以看到最后输出的是err4,第一个失败的返回Valueconstp13=Promise.all([p1,p4,p5]).then(console.log).catch(console.log)//err4源码实现Promise.myAll=(promises)=>{returnnewPromise((rs,rj)=>{//计数器letcount=0//存储结果letresult=[]constlen=promises.lengthif(len===0){returnrs([])}promises.forEach((p,i)=>{//注意有些数组项可能不是Promise,需要手动转换Promise.resolve(p).then((res)=>{count+=1//收集每个Promise的返回值result[i]=res//当所有的Promise都成功后,将返回的Promise结果设置为resultif(count===len){rs(result)}//只要监听数组item中的一个Promisecatch失败,我们自己返回的Promise也会失败}).catch(rj)})})}//Testconstp1=Promise.resolve(1)constp2=newPromise((resolve)=>{setTimeout(()=>resolve(2),1000)})constp3=newPromise((resolve)=>{setTimeout(()=>resolve(3),3000)})constp4=Promise.reject('err4')constp5=Promise.reject('err5')//1.所有Promise都成功constp11=Promise.myAll([p1,p2,p3]).then(console.log)//[1,2,3].catch(console.log)//2.有一个失败的Promiseconstp12=Promise.myAll([p1,p2,p4]).then(console.log).catch(console.log)//err4//3.两次Promise失败,可以看到最后输出的是err4,第一次失败的返回值为constp13=Promise.myAll([p1,p4,p5]).then(console.log).catch(console.log)//err4//和原来的Promise一致.allreturnPromise.allSettled简单回顾有时候,我们希望等到一组异步操作结束,而不管每个操作是成功还是失败,再进行下一步。显然Promise.all(只要有一个失败,结果就会进入失败状态)是不适合的,所以Promise.allSettledPromise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个Promise对象,并返回一个新的Promise对象。只有当参数数组中的所有Promise对象都发生状态变化时(无论是fulfilled还是rejected),返回的Promise对象才会改变状态。一旦发生状态变化,状态将始终满足,不会变为拒绝状态。还是以上面的例子为例,我们看看它和Promise.all有什么不同constp1=Promise.resolve(1)constp2=newPromise((resolve)=>{setTimeout(()=>resolve(2),1000)})constp3=newPromise((resolve)=>{setTimeout(()=>resolve(3),3000)})constp4=Promise.reject('err4')constp5=Promise.reject('err5')//1.所有Promise都成功constp11=Promise.allSettled([p1,p2,p3]).then((res)=>console.log(JSON.stringify(res,null,2)))//输出/*[{"status":"fulfilled","value":1},{"status":"fulfilled","value":2},{"status":"fulfilled","value":3}]*///2.有一个失败的Promiseconstp12=Promise.allSettled([p1,p2,p4]).then((res)=>console.log(JSON.stringify(res,null,2)))//输出/*[{"status":"fulfilled","value":1},{"status":"fulfilled","value":2},{"status":"rejected","reason":"err4"}]*///3.两个Promise失败constp13=Promise.allSettled([p1,p4,p5]).then((res)=>console.log(JSON.stringify(res,null,2)))//输出/*[{"status":"fulfilled","value":1},{"status":"rejected","reason":"err4"},{"status":"rejected","reason":"err5"}]*/可以看到:无论是全部成功还是部分失败,最终都会进入Promise.allSettled的.then回调的最终返回值,均成功而失败的item有一个status属性,成功时值为fulfill,失败时rejected。最终返回值包含成功的value属性,失败的reason属性。newPromise((rs,rj)=>{letcount=0letresult=[]constlen=promises.length//如果数组为空,直接返回空数据if(len===0){returnresolve([])}promises.forEach((p,i)=>{Promise.resolve(p).then((res)=>{count+=1//成功属性设置result[i]={status:'fulfilled',值:res}if(count===len){rs(result)}})。catch((err)=>{count+=1//失败属性设置result[i]={status:'rejected',reason:err}if(count===len){rs(result)}})})})}//测试constp1=Promise.resolve(1)constp2=newPromise((resolve)=>{setTimeout(()=>resolve(2),1000)})constp3=newPromise((resolve)=>{setTimeout(()=>resolve(3),3000)})constp4=Promise.reject('err4')constp5=Promise.reject('err5')//1.所有的Promise都成功了constp11=Promise.myAllSettled([p1,p2,p3]).then((res)=>console.log(JSON.stringify(res,null,2)))//输出/*[{"status":"fulfilled","value":1},{"status":"fulfilled","value":2},{"status":"fulfilled","value":3}]*///2.有是一个失败的Promiseconstp12=Promise.myAllSettled([p1,p2,p4]).then((res)=>console.log(JSON.stringify(res,null,2)))//output/*[{“状态”:“已完成”,“价值”:1},{"status":"fulfilled","value":2},{"status":"rejected","reason":"err4"}]*///3.两个promise失败了constp13=Promise.myAllSettled([p1,p4,p5]).then((res)=>console.log(JSON.stringify(res,null,2)))//输出/*[{"status":"fulfilled","value":1},{"status":"rejected","reason":"err4"},{"status":"rejected","reason":"err5"}]*/Promise.Race简单回忆一下,Promise.race()方法也是将多个Promise实例包装成一个新的Promise实例constp=Promise.race([p1,p2,p3])只要p1,p2,p3中有一个实例即可先改变状态,p的状态会随之改变。第一个改变的Promise实例的返回值被传递给p的回调函数。constp1=newPromise((resolve,reject)=>{setTimeout(resolve,500,1)})constp2=newPromise((resolve,reject)=>{setTimeout(resolve,100,2)})Promise。race([p1,p2]).then((value)=>{console.log(value)//2})Promise.race([p1,p2,3]).then((value)=>{console.log(value)//3})源码实现聪明的你一定马上就知道怎么实现了。只要你知道哪个实例先改变,那么Promise.race就会跟随结果,那么你可以写下面的代码Promise.myRace=(promises)=>{returnnewPromise((rs,rj)=>{promises.forEach((p)=>{//将p包裹一次,防止非Promise对象//并对齐到monitor,我们将自己返回的Promise的resolve和reject传递给p,哪个先改变状态,whatstate我们返回的Promise将是Promise.resolve(p).then(rs).catch(rj)})})}//测试它constp1=newPromise((resolve,reject)=>{setTimeout(resolve,500,1)})constp2=newPromise((resolve,reject)=>{setTimeout(resolve,100,2)})Promise.myRace([p1,p2]).then((value)=>{控制台.log(value)//2})Promise.myRace([p1,p2,3]).then((value)=>{console.log(value)//3})最后,也许你和我未曾谋面,但很有可能很快就会见到你,希望这里能成为你的栖息地。我愿与你一起收获快乐,一起去成长。以上是第一次手写实现原理分析!欢迎纠正任何可能的错误和问题