摘要本系列是关于Koa框架的,目前重点版本为Koav1。主要分为以下几个方面:Koa源码分析(一)——generatorKoa源码分析(二)——co实现Koa源码分析(三)——中间件机制实现co什么是大名鼎鼎的co?是同济大神基于ES6的一些新特性开发的异步流程控制库。基于它开发的koa被认为是未来主流的web框架。Koa是基于co实现的,co使用了ES6的generator和promise特性。如果还不明白,可以查看阮一峰老师的《ECMAScript 6 入门 --- Generator》和《ECMAScript 6 入门 --- Promise》。目前co已经升级到4.X版本,代码进行了大规模的重构。我们主要关注co(4.X)的实现思路和源码分析。用法示例co(function*(){vara=yieldPromise.resolve('one');console.log(a);varb=yieldPromise.reslove('two');console.log(b);return'three';}).then((value)=>console.log(value));//one//two//threeco(function*(){varres=yield[Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)];returnres;}).then((value)=>console.log(res));//[1,2,3]根据co的函数,它作为一个异步流程控制函数,自动调用生成器对象的next()方法,实现对生成器函数的操作,并返回最终的操作结果。如果要涉及到co的实现细节,我们会有以下问题:如何依次调用next()方法,如何将yield后的操作异步结果返回给对应的变量,如何返回finalgenerator函数自己返回值,然后我们面对上面的问题,分析TJ大神的源码来分析co源码的流程控制functionco(gen){//保持当前函数的上下文varctx=这个;//拦截co输入的参数,去掉arguments中的第一个参数,即gen对象,其余参数作为gen的输入参数varargs=slice.call(arguments,1);//返回一个Promise对象,即最外层的Promise对象returnnewPromise(function(resolve,reject){//判断传入的gen是否为函数,如果是则执行,并将结果赋值给gen对象//如果没有,则不执行if(typeofgen==='function')gen=gen.apply(ctx,args);//根据生成器函数的执行结果是否有next字段,判断gen是否为生成器迭代器对象//如果不是,则调用resolve返回最外层Promise对象的状态if(!gen||typeofgen.next!=='function')returnresolve(gen);//If它是一个generator迭代器对象,开始控制gen.next()方法的调用onFulfilled();//两个目的//1.generator函数的执行入口//2.作为所有内部的resolve方法Promise对象处理异步结果并继续调用下一个Promise函数onFulfilled(res){varret;try{//gen运行到yield被挂起开始处理异步操作,并将异步操作的结果返回给ret.valueret=gen.next(res);}catch(e){//如果报错,直接调用reject返回外围Promise对象的状态,并发送错误对象returnreject(e);}//将gen.next的执行结果传递给next函数,依次调用gen.next方法next(ret);返回空值;}//将所有Promise内部对象的reject方法视为异步结果,继续调用下一个Promise函数onRejected(err){varret;尝试{ret=gen.throw(err);}catch(e){//如果报错,直接调用reject返回外围Promise对象的状态,并将错误对象发送出去returnreject(e);}//将gen.throw的执行结果传入next函数,实现gen.next方法的串行调用next(ret);}//实现gen.next的串行调用核心函数next(ret){//判断内部Promise是否全部执行完毕//如果执行完成,直接调用resolve改变外围Promise的状态和返回最终的返回值【问题3】if(ret.done)returnresolve(ret.value);//如果执行不完整,调用toPromise方法,将上一个Promise的返回值转为Promise对象//详情见toPromise方法varvalue=toPromise.call(ctx,ret.value);//根据value转换过来的Promise对象的两种状态,执行下一个next方法if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);//抛出一个不符合转换规则的类型的值returnonRejected(newTypeError('Youmayonlyyieldafunction,promise,generator,array,orobject,'+'butthefollowingobjectwaspassed:"'+String(ret.value)+'"'));}});}分析完源码,我们可以总结一下generator函数中co串行调用yield的过程如下:进入外围Promise并传入入口onFilled()方法,运行generator函数到第一个yield,执行异步操作在yield之后,将结果传递给next方法。如果next传入的result的done为true,则返回外围Promise的resolveifnext如果传入的result的done为true,则返回该值(即yield之后的对象)是否可以转化为内部Promise对象。如果无法转换,则会抛出错误。如果外部Promise的拒绝可以转换为Promise对象,则所有内部Promise都会被转换。并行执行,通过then(onFilfilled,onRejected)开始执行,调用onFilfilled()或onRejected()内部的next()方法,实现yield的串行执行,将yield后面的对象传递给next(),依次重复。所有yield执行返回,将最终的返回值返回给外围Promise的resovle方法,结束co对generator函数的调用。yield之后的对象被转换为Promise。生成器函数的next()方法可以在co中逐步调用,转化为内部的Promise。会很关键,但是源码是怎么改造的呢?哪些对象可以变形?接下来,让我们看一下源代码。functiontoPromise(obj){//确保obj有意义if(!obj)returnobj;//如果是Promise对象,直接返回if(isPromise(obj))returnobj;//如果是生成器函数或者生成器对象,传入一个新的co,返回新co的外层Promise//作为当前co的内部Promise,从而实现多级调用if(isGeneratorFunction(obj)||isGenerator(obj))returnco.call(this,obj);//如果是函数,返回thunk规范的函数if('function'==typeofobj)returnthunkToPromise.call(this,obj);//如果是数组,将数组中的每一个元素都转化为一个内部的Promise,并返回Promise。所有并行操作if(Array.isArray(obj))returnarrayToPromise.call(this,obj);//如果是对象,遍历对象中每个key对应的value,转换成Promise.all并行操作if(isObject(obj))returnobjectToPromise.call(this,obj);returnobj;}functionthunkToPromise(fn){varctx=this;returnnewPromise(function(resolve,reject){fn.call(ctx,function(err,res){if(err)returnreject(err);if(arguments.length>2)res=slice.call(arguments,1);resolve(res);});});}functionarrayToPromise(obj){//Array.map并行计算返回每个元素Promise返回Promise.all(obj.map(toPromise,this));}functionobjectToPromise(obj){varresults=newobj.constructor();varkeys=Object.keys(obj);var承诺=[];for(vari=0;i
