本文仅发表于个人博客和SegmentFault社区个人专栏,转载个人博客请注明出处:https://zengxiaotao.github.ioSegmentFault个人专栏:https://segmentfault。com/blog...写在前面,学习nodejs当然少不了学习框架,毕竟原生API比较底层。第一个接触的是Koa。看一下官网关于node.js的下一代web框架的描述我翻译为:基于node.js的下一代web开发框架。看起来很神奇!Koa是一个轻量级的框架,本质上是提供一个架子,通过各种中间件的级联来实现特定的功能。借助promise和generator,koa很好的解决了异步组合的问题。那么又是什么。学习koa,就必须学习co模块。co模块可以将异步解放为同步。co函数接受一个生成器函数作为参数,并在函数内部自动执行yield。co源码分析用的co模块版本号是4.6.0首先看一些判断对象类型的函数varslice=Array.prototype.slice;//引用数组切片方法functionisObject(val){returnObject==val.constructor;}这两个应该不用说了吧。..functionisPromise(obj){return'function'==typeofobj.then;}判断一个对象是否是promise实例,判断的依据也很简单,根据“鸭子类型”,判断这个对象是否有一个thenmethodfunctionisGenerator(obj){return'function'==typeofobj.next&&'function'==typeofobj.throw;}同理,判断一个对象是generator实例时,只需要判断该对象是否有一个next方法和一个throw方法。函数isGeneratorFunction(obj){varconstructor=obj.constructor;如果(!构造函数)返回假;if('GeneratorFunction'===constructor.name||'GeneratorFunction'===constructor.displayName)returntrue;returnisGenerator(constructor.prototype);}判断是否是生成器函数,只需要判断这个函数是否是GeneratorFunction函数的实例即可。稍后将值包装到promise实例时将使用上面的内容。查看co模块的输出部分module.exports=co['default']=co.co=co所以下面三种用法是等价的varco=require('co')//(1)varco=require('co').co//(2)varco=require('co').default//(3)然后是主要事件,co函数。函数co(gen){varctx=this;//保存函数的执行上下文对象varargs=slice.call(arguments,1)//传递给gen函数的参数//返回一个promise实例returnnewPromise(function(resolve,reject){//生成一个generatorinstance根据传入的generatorfunctionif(typeofgen==='function')gen=gen.apply(ctx,args);//如果生成的gen不是generatorinstance//promise直接变成resolved状态if(!gen||typeofgen.next!=='function')returnresolve(gen);//执行onFulfilled方法onFulfilled();functiononFulfilled(res){varret;try{//执行gen的next方法ret=gen.next(res);}catch(e){returnreject(e);}//并将这个值传递给下一个函数next(ret);}functiononRejected(err){varret;try{ret=gen.throw(err);}catch(e){returnreject(e);}next(ret);}functionnext(ret){//如果gen执行完毕,ret.done变为true,则this的状态promise实例//如果(ret.done)returnresolve自然会被resolved(ret.value);varvalue=toPromise.call(ctx,ret.value);//将值重新打包成一个promise实例//将新返回的promise实例的resolve方法设置为onFulfilled函数,再次执行next方法,这样会自动调用generator实例的next方法if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);returnonRejected(newTypeError('你只能产生一个函数、promise、生成器、数组或对象,'+'但传递了以下对象:"'+String(ret.value)+'"'));}});}上面co模块实现了generator实例的next方法的自动执行下面我们看看co是如何将一个值转换成promise实例的。函数toPromise(obj){如果(!obj)返回obj;//如果传入的obj是一个false值,则返回这个false值如undefined,false,nullif(isPromise(obj))returnobj;//如果是Promise实例,返回这个promise实例if(isGeneratorFunction(obj)||isGenerator(obj))returnco.call(this,obj);//如果是生成器函数或生成器if('function'==typeofobj)returnthunkToPromise.call(this,obj);//如果它是一个thunk函数if(Array.isArray(obj))returnarrayToPromise.call(this,obj);//如果是数组if(isObject(obj))returnobjectToPromise.call(this,obj);//如果是普通对象,则返回obj;//如果是原始值,返回这个原始值。}然后依次读取每个函数。函数thunkToPromise(fn){varctx=this;//保存函数上下文对象//返回一个promise实例returnnewPromise(function(resolve,reject){//执行传入的thunk函数//thunk函数接受一个回调函数作为参数fn.call(ctx,function(err,res){//如果thunk函数运行错误//promise实例变为rejected,执行reject函数,即co函数中定义的onRejected函数,同if(err)returnreject(err);//获取冗余参数if(arguments.length>2)res=slice.call(arguments,1);//promise状态变为resolved,执行resolve函数,即onFulfilledfunctionresolve(res);});});}所以,总结一下,如果generator中的yield后面跟着一个thunk函数,这个thunk函数接受一个回调参数作为参数,并且在这个回调函数中定义了什么时候返回promise状态变成resolved或者rejected,functionarrayToPromise(obj){//Promise.all方法返回一个新的promise实例//如果obj是一个数组,则将每个元素包装成一个promise实例//如果每个promiseif都变为resolved状态//那么状态的newpromiseinstancereturnedbecomesreslovedstate//传递给resolve函数的参数是由return之前每个promise的返回值组成的数组Promise.all(obj.map(toPromise,this));}同样,如果obj是一个数组,即yield语句后面的表达式的值是一个数组,然后执行Promise.all方法将数组中的每一项变成一个promise实例。具体方法如下:使用toPromise方法将obj数组中的每一项包装成一个promise实例如果上一步中数组中有元素不是promise实例,Promise.all方法会调用Promise.resolve方法将其转换为promise实例。Promise.all方法返回一个新的promise实例。只有当promise实例数组中的所有实例的状态都已解决时,这个新的promise实例的状态才会变为已解决。一旦数组中的一个promise实例变为rejected,新的promise实例也会变为rejected。当返回的新promise实例状态变为resolved时,传递给它的resolve函数的参数是一个数组,由之前数组中的每个promise实例调用resolve函数的返回值组成。如果返回的新promise状态变为rejected,那么传递给reject函数的参数就是数组中第一个变为rejected并执行reject函数的promise实例的返回值。真的是一口一大口,多看几遍应该就明白了。最后看看ret.value是不是对象,co模块是如何把它变成promise实例的。functionobjectToPromise(obj){//定义一个空对象varresults=newobj.constructor();//获取obj的所有属性varkeys=Object.keys(obj);//用于生成每个属性值对应的promise实例varpromises=[];for(vari=0;i
