Promise由浅入深介绍.pdf
在我的上一篇文章里着重介绍了 async的相关知识,对promise的提及甚少,现在很多面试也都要求我们有手动造轮子的能力,所以本篇文章我会以手动实现一个promise的方式来发掘一下Promise的特点.简单版Promise首先我们应该知道Promise是通过构造函数的方式来创建的(newPromise(executor),并且为 executor 函数 传递参数:function P r o(executor)executor(resolve,reject);function)f(Auction reject。)再来说一下Promise的三种状态:pending-等待,resolve-成功,reject-失败,其中最开始为pending状态,并且一旦成功或者失败,Promise的状态便不会再改变,所以根据这点:fuMtion Proi/vu(exector)let=this;八力认g,;cxecixtoKvesoIve.bi八4(this),reject.biiad(this);缶 八ctio八 resolve)if(_tkis4status=工hk.料states=full1function reject。if(_this.iistatus=pe八力hg)_tkis.$tatus=fail1其中$status来记录P rom ise的状态,只有当p ro m ise的状态未p e n d in g时我们才会改变它的状态为fu ll或者fa il,因为我们在两个sta tu s函数中使用了 this,显然使用的是P rom ise的一些属性,所以我们要绑定resolve与re je ct中的th is为当前创建的Promise;这样我们最最最基础的Prom ise就完成了(只有头部没有四肢)Promise 高 级-.then接着,所有的P rom ise实例都可以用.th e n方法,其中.th e n的两个参数,成功的回调和失败的回调也就是我们所说的resolve和reject:fuMtioia Proi(executor)let _this=this;=pc 八 dMg;failCallBack=IA 八de fined;J:kissucceCallback=undefined;tkis.crror=八 dcFi八 ed;exec(Ator(Resob/e.bi八4(fhis),re/ect.fe(n(_this);Fcmctio八 resolve(parais)if status=pe八力hg)J:his.排 tatas=success)_tkissucces$Callback(parai)fu八cticm reject(params)if(Jhis.ftatus=八力认g。J:kis4itatus-fail1_tkis.failCallBack(parakv$)P忆。prototypehe八=fuictiofullj fail)thissuccessCallback=fullths.failCallBack=fail);/测试代W5 n e w%。3行 八 比/。八(rej)s c t T i k w c o u t Q =成功以 3(9).t h e H(r e s =coole.log(res)讲一下这里:可以看到我们增加了 failCallBack和successCallback,用来储存我们在then中回调,刚才也说过,then中可传递一个成功和一个失败的回调,当P的状态变为resolve时执行成功回调,当P的状态变为reject或者出错时则执行失败的回调,但是具体执行结果的控制权没有在这里。但是我们知道一定会调用其中的一个。executor任务成功了肯定有成功后的结果,失败了我们肯定也拿到失败的原因。所以我们可以通过params来传递这个结果或者error reason(当然这里的params也可以拆开赋给Promise实例)其实写到这里如果是面试题,基本上是通过了,也不会有人让你去完整地去实现error:用来存储,传递reject信息以及错误信息Promise 进阶我想我们最迷恋的应该就是Promise的链式调用吧,因为它的出现最最最大的意义就是使我们的callback看起来不那么hell(因为我之前讲到了 async比它更直接),那么为什么then能链式调用呢?then 一定返回了一个也具有then方法的对象我想大家应该都能猜到.then返回的也一定是一个promise,那么这里会有一个有趣的问题,就是.then中返回的到底是一个新promise的还是链式头部的调用者 9 9 9 9 9从代码上乍一看,Prom/se.theFi(.).catch(.)像是针对最初的Promise对象进行了一连串的方法链调用。然而实际上不管是then还 是catch方法调用,都返回了一个新的promise对象。简单有力地证明一下var begi八Promise=new Proiise(function.(resolve)the八PK O FV USC=beg/八POFvue.thc八(function(value)coisole.log(value);catch Promise=theiaPoiise.catcfuictioi(error)co八 so/c.e。心。r););coo(edog(begi.Proiise/=thenProm/se);/=truecon$ole.log(theiaProiise/=catchPromise);/=true显而易见promise返回的是一个新的而非调用者不过这样的话难度就来了,我们看下面代码:function Segi八()(return new Promisefresole=setTii,eout(_=resolvefirst1)ZOOO)1begii(.thei(data=cosote.log(data)return new Prom/sefresole=1).仍。nes=cok$ole.log(re$)!);我们知道最后的then中函数参数永远都不会执行,为什么说它难呢,想一下,之所以能链式调用是因为.then。执行之后返回了一个新的promise,一定注意,我说的新的 promise 是 then。所返回而不是 data=return new Promise.(这只是then的一个参数),这样问题就来了,我们从刚才的情况看,知道只有第一个.then中的状态改变时第二个then中的函数参数才会执行,放到程序上说也就是需要第一个.then中返回的promise状态改变!即:begi().theia(data=coole.log(data)return new Prokvise(resolve=setTimeout(_=-esolveCtwo、1OOO)1)=conso/eog(?es));直接从代码的角度上讲,调用了第一个.then中的函数参数中的resolve之后第一个.then。返回的promise状态也改变了,这句话有些绕,我用一张图来讲:那么问题就来了,我们如何使得P2的状态发生改变通知P1?其实这里用观察者模式是可以的,但是代价有点大,换个角度想,其实我们直接让 P2中的resolve等于P1中的resolve不就可以了?这样P 2 中调用了 resolve之后同步的P l也相当于onresolve 了,上代码:FIA八ctio八 let _jthis=this;料s tatus=peidig;_tkis.faitCallBack=undefi八 cd;_tkis.$ucce$Callback=八 deFMcd;result=八 defined;_th(s.error=undefined;setT沁=execbitoJ:hisHesob/e.bi八fhis.seject.bi八d(_tkis);)ProMi.pKototgpe.theA=fa八ctioc(fudL fail)let MwProkvi=MVJ Pemi(_=);thisucceCallback=full;this.failCallBack.=fail;协 MstxcccssDefer=八 CWPKO M,CS。仇.切 八 d(ncwProFvu);this.failPefer=MA/Proivi reject.bid(MwProi);return newProwu);Provi.prototype.reso(ve=fuiactioi(para)let _this=this;if(_tk/s.$s勿仇 ws,成 功 5 0 0).仍0八(匕4=co 八s o/c./og(?es);return,第一个.防e八成功,).theH(res=conso/eog(%s);return new Provi(fuiactioy(resolve)setTMACot(_=Kcsoke。第二个.the八 成 功SOO)1).theH(res=co 八s o/c./og(侬)return M W Prokvxfukxctioresolve,reject)setTii y/ccC第三个失败,OOO)1),协。八(%s=rescosole.log(res),rej=c。八so/eog(匕4);Promise 完善其实做到这里我们还有好多好多没有完成,比如错误处理,reject处理,catch实现.all实现,.race实现,其实原理也都差不多,(all和 race以及resolve和 reject其实返回的都是一个新的Promise),错误的传递?还有很多细节我们都没有考虑到,我这里写了一个还算是比较完善的:Function pKOMi(ex0CtOK)let _tkis=this;=pending;_this.failCalll3ack=uiadefiMd;jtkissuccessCallback=八 defined;_this.eor=认八 fined;setTiieout(_=try(exec(Ato _this.onReso(ve.bind(_this),_this.onReject.bi八,(_fhis)catch(e)_thrs.error=e;if(_this.callBackPefer&_Ms.callBackDefer.fail)_tkis.calll3ackPefer.fail(e)eke if(_tkis._catck)_ikis._catck(e)eke(throw M W Errorfn.catch)!)Povvi.prototype=constructor.Prowi,0八Rcso/vc:fukctiok(parais)if(tkis4$status=peidiig)this4istatus=success1;tkisxesolve(parais),resolve:fuMtioi(parais)let _this=this;let succeCallback=_thk.sccessCH伤 ck;if(successCallback _tkis.dcfesucccssCaHback.bM4(_this,)bdefer.fuictioia(callBack)let _this=this;let result;let defer=_this.callBacklefersuccess;if(一tkk.$sats=fail1&!_tkis.catclaEirrorFuiac)defer=_ths.callBackPefer.fail;)try(result=callBack();catch(e)result=e;defer=J:kis.callBackDefer.fail;if(result&result,八 八ceof Prom/-)丫esbdt.the八(_this.caHBackDefeKSb(ccess,_this.callBackDefer.fail);returndcfe(esbdt)boR.eject:ftmctio八(error)if(协is.$stnt s=,pc八力认g,)(thistatus=fail1;this.reject(error)卜reject:fuMtiok(error)let _tkis=this;_t is.error=error;let failCallBack=_tkis.failCall13ack;let,catch=_ihis._catcKif(failCallBack)_this.defcr(failCallBack.bikd(_thisJ error);else if(_catck)_catcM(error)eke(=throw M W Errorf八 catch prom/$e/),O),协e八:functio八(success=()=),fail)let _this=this;let resetFail=e=e;if(fail)(resetFail=fail;Jhis.catckErorFuM=true;)let HewPromise=八cw Prokvi(_=);_tkis.callBackDefer=success:ncwPronAisc.。八Rcso/ve.bi八dSewProM/use),fail:八ewPKOvuse.o八Raec七 罚 八d(newPron/usc);jthissuccessCallback=success_this.fai(Ca(lBack=re$etFail;return newProm/sebcatch:fuictioi(catchCallBack=()=)this._catch=catchCallBack./测试代码task().协皿 侬=c t m s o/c./o g :+胆)r e t u r n,第一个仍e 1)工 人。八(烟=r e t u r n M W Prokvi(re$=s e t T/m e o u t(_ =匕=。第二个 t h e ),50 0 0 ).t h e H(r e s =coole.log(ires)1)私 八(烟=r e t u r n ne w Pr o m/f f s u c,fa i l)=T而e o u t(_ =failCthen 失败:)4。6!)1).协(res=cooledog(iko)!)物 皿_=&()=return new rej)setT/meout(_=rejCproiise reject1),3000)1)1)工 e八0M e八0,协0八(_=)rej=console.log(rej);retuna rej+处理完成,!).tMenfres=co 八so/eog(%s);/故意出错co 八sole.log(ppppppp).theses=&句=coole.log(rej);/再次抛错cosole.log(oooooo).catch(e=co ns o/cog(e)1)还有一段代码是我将所有的.then全部返回调用者来实现的,即全程都用一个promise来记录状态存储任务队列,这里就不发出来了,有兴趣可以一起探讨下.有时间会再完善一下all,race,resolve.不过到时候代码结构肯定会改变,实在没啥时间,所以讲究看一下吧,欢迎交流