你能学到什么如何使用Generator+Promise实现异步编程基本原则说到异步编程,你会想到async和await,但那只是Generator的语法糖。dva中有一个Effect的概念,就是利用Generator来解决异步请求的问题。再来说说Generator+Promise异步编程是如何实现的:在开始之前,我们需要了解一些基本概念:Generator在ES6中作为协程的解决方案,处理异步编程的具体实现。其特点是:在Generator中可以使用yield关键字配合实例gen调用next()方法来划分执行其内部语句。简而言之:next()调用一次,yield语句执行一句,next()调用时,yield语句依次执行。Promises表示异步操作的最终状态(完成或失败),以及它返回的值。参考Promise-MDN那么,使用Generator和Promise进行异步编程的原理是什么?因为Generator本身的yield语句是单独执行的,我们利用这一点在yield语句中返回一个Promise对象。在Generator中第一次调用next()后,假设返回值叫做result,那么result.value就是我们在yield语句中为Promise对象定义的注释:这一步,我们已经暂停了原来的执行process并转向执行Promise的内容,已经实现了对异步代码执行的控制,因为如果此时我们不继续执行next(),generator当前执行的yield后面的内容将不会继续执行执行。这样就达到了我们需要的效果。接下来,执行完当前Promise,让代码继续执行,直到遇到下一个yield语句:这一步是最关键的,那我们怎么做呢:第一步:在当前Promise的then()方法中,继续执行gen.next()Step2:当gen.next()返回的result.done===true时,我们得到result.value【也就是一个新的Promise对象】再次执行,继续上面的step1在其then()方法中,直到result.done===false。此时调用resolve()会导致promise的状态发生变化,因为所有的yield语句都已执行。第1步确保我们可以转到下一个yield语句。Step2确保下一个yield语句的执行不会被打断,直到Generator中的最后一个yield语句执行完毕。流程示意图:co的具体实现是名师TJ实现的Generator的二次封装库,那我们就从co库中的一个demo开始,了解我们整个异步请求的封装实现:co(function*(){yieldme.loginAction(me.form);...});这里我们引入co库,用co来包装一个生成器(generator)对象。接下来看看co对包装生成器函数做了什么co(gen){//1.获取当前co函数的执行上下文,得到参数列表varctx=this;varargs=slice.call(arguments,1);//2.返回一个Promise对象returnnewPromise(function(resolve,reject){//确定并使用ctx:context(上下文环境)和arg:arguments(参数列表)来初始化生成器并复制到gen//注意://gen=gen.apply(ctx,args)//当我们调用gen.next()时,返回的是一个指针,实际值是一个对象//对象的形式:{done:[false|true],value:''}if(typeofgen==='function')gen=gen.apply(ctx,args);//当返回值不是gen或gen.next的类型时isnotfunction[其实就是判断是否是generator]//当前promise状态设置为resolve结束if(!gen||typeofgen.next!=='function')returnresolve(gen);//否则,执行onFulfilled()onFulfilled();});}总结一下这里发生的事情返回一个promise,将promise中的wrappedgenerator实例化为一个指针,指向generator中第一个yield语句判断实例化的指针是否由生成器存在:如果没有yield语句,则指针不存在。判断指针gen.next()方法是否为函数:如果不是函数,则证明gen.next()无法执行。如果其中一个条件不满足,则将promise的状态设置为resolve;否则执行onFulfilled()。接下来我们看onFulfilled()函数在on上的实现Fulfilled(res){//当执行onFulfilled时,定义一个ret用来存放gen.next(res)执行后的指针对象varret;试试{ret=gen.next(res);//在这里,yield语句抛出的值是{value:me.loginAction(me.form),done:false}}catch(e){returnreject(e);}//将ret对象传递给我们在promise中定义的next方法next(ret);返回空值;}总结一下,onFulfilled的主要工作就是执行gen.next()来执行代码,直到yield语句将执行后返回的结果传入我们自定义的next()方法中,接下来我们看下next()方法functionnext(ret){//进入next,先判断我们传入的ret的done状态://情况一:ret.done=true表示我们generator中的yield语句全部执行完//然后传ret.value进入resolve(),promise状态变为resolved,整个过程结束。如果(ret.done)返回resolve(ret.value);//情况2:currentret.done=false表示generator还没有执行完所有的yield语句,那么这个时候//我们结合currentcontext和ret.value传给toPromise,转换为对应的Promiseobject`value`varvalue=toPromise.call(ctx,ret.value);if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);//当value确实是promise对象时,返回value.then(onFulfilled,onRejected)//我们重新进入生成器,执行下一条yield语句returnonRejected(newTypeError('Youmayonlyyieldafunction,promise,generator、数组或对象,'+'但传递了以下对象:"'+String(ret.value)+'"'));}总结一下,next的主要工作就是判断最后一个yield语句的执行结果,将yield结果的value值【其实就是我们要异步执行的Promise】执行value的then方法并重新进入onFulfilled方法,在onFulfilled中,我们会再次进入当前方法。这样的循环调用实现了generator和Promise执行的切换,使得Promise的内容按照我们定义的顺序执行。可能有同学对这里的toPromise方法有一些疑惑。我会先贴出代码。函数toPromise(obj){如果(!obj)返回obj;如果(isPromise(obj))返回obj;如果(isGeneratorFunction(obj)||isGenerator(obj))returnco.call(this,obj);if('function'==typeofobj)returnthunkToPromise.call(this,obj);如果(Array.isArray(obj))返回arrayToPromise.call(this,obj);如果(isObject(obj))返回objectToPromise.call(this,obj);returnobj;}其实这个函数做的就是根据不同的类型进行转换,这样最终输出的类型就是一个Promise。具体的转换细节可以参考co库的源码。至此,实现了异步操作的控制。最后是Dendoink,StrangeDanceWeekly的原作者,Nuggets[联合编辑/小册子作者]。对于技术人员:技术是单兵的作战能力,技术是使用能力的方法。得心应手,出神入化就是艺术。在前端娱乐圈,我想做一个优秀的人民艺术家。扫描二维码关注公众号前端小霸王我在这里等你:
