You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constconvert=require('koa-convert');module.exports=classApplicationextendsEmitter{use(fn){if(typeoffn!=='function')thrownewTypeError('middleware must be a function!');if(isGeneratorFunction(fn)){deprecate('Support for generators will be removed';fn=convert(fn);}debug('use %s',fn._name||fn.name||'-');this.middleware.push(fn);returnthis;}}
constco=require('co')constcompose=require('koa-compose')module.exports=convertfunctionconvert(mw){if(typeofmw!=='function'){thrownewTypeError('middleware must be a function')}if(mw.constructor.name!=='GeneratorFunction'){returnmw}constconverted=function(ctx,next){returnco.call(ctx,mw.call(ctx,createGenerator(next)))}converted._name=mw._name||mw.namereturnconverted}
functionco(gen){varctx=this;varargs=slice.call(arguments,1);returnnewPromise(function(resolve,reject){if(typeofgen==='function')gen=gen.apply(ctx,args);if(!gen||typeofgen.next!=='function')returnresolve(gen);onFulfilled();functiononFulfilled(res){varret;try{ret=gen.next(res);}catch(e){returnreject(e);}next(ret);returnnull;}functiononRejected(err){varret;try{ret=gen.throw(err);}catch(e){returnreject(e);}next(ret);}functionnext(ret){if(ret.done)returnresolve(ret.value);varvalue=toPromise.call(ctx,ret.value);if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);returnonRejected(newTypeError('You may only yield a function, promise, generator, array, or object, '+'but the following object was passed: "'+String(ret.value)+'"'));}});}
koa的基础结构
首先,让我们认识一下koa框架的定位——koa是一个精简的node框架:
koa框架的核心目录如下:
koa源码基础骨架
application.js
application.js是koa的主入口,也是核心部分,主要干了以下几件事情:
context.js
context.js主要干了两件事情:
request.js
request对象基于node原生req封装了一系列便利属性和方法,供处理请求时调用。所以当你访问ctx.request.xxx的时候,实际上是在访问request对象上的setter和getter。
response.js
response对象基于node原生res封装了一系列便利属性和方法,供处理请求时调用。所以当你访问ctx.response.xxx的时候,实际上是在访问response对象上的setter和getter。
4个文件的代码结构如下:
koa工作流
Koa整个流程可以分成三步:
初始化阶段
new初始化一个实例,包括创建中间件数组、创建context/request/response对象,再使用use(fn)添加中间件到middleware数组,最后使用listen 合成中间件fnMiddleware,按照洋葱模型依次执行中间件,返回一个callback函数给http.createServer,开启服务器,等待http请求。结构图如下图所示:
请求阶段
每次请求,createContext生成一个新的ctx,传给fnMiddleware,触发中间件的整个流程。
响应阶段
整个中间件完成后,调用respond方法,对请求做最后的处理,返回响应给客户端。
koa中间件机制与实现
koa中间件机制是采用koa-compose实现的,compose函数接收middleware数组作为参数,middleware中每个对象都是async函数,返回一个以context和next作为入参的函数,我们跟源码一样,称其为fnMiddleware在外部调用this.handleRequest的最后一行,运行了中间件:
fnMiddleware(ctx).then(handleResponse).catch(onerror);
以下是koa-compose库中的核心函数:
我们不禁会问:中间件中的next到底是什么呢?为什么执行next就进入到了下一个中间件了呢?中间件所构成的执行栈如下图所示,其中next就是一个含有dispatch方法的函数。在第1个中间件执行next时,相当于在执行dispatch(2),就进入到了下一个中间件的处理流程。因为dispatch返回的都是Promise对象,因此在第n个中间件await next()时,就进入到了第n+1个中间件,而当第n+1个中间件执行完成后,可以返回第n个中间件。但是在某个中间件中,我们没有写next(),就不会再执行它后面所有的中间件。运行机制如下图所示:
我们比较一下koa框架的中间件和mtop框架的中间件运行顺序会有何不同:
以下是mtop client源码中关于中间件的运行机制部分,使用了
_sequence
函数来包装整个请求过程(包括确定请求方法、请求类型、获取token、签名、中间件、发送请求等流程),Mtop.prototype._sequence
方法中,如果传入数组中的元素仍然是数组时,则按顺序对其数组中的各项元素进行依次处理。如that.middlewares
是中间件处理的数组,因此在请求过程中,也是对于每个中间件进行依次处理的,和koa的洋葱模型有些不同。koa-convert解析
在koa2中引入了koa-convert库,在使用use函数时,会使用到convert方法(只展示核心的代码):
koa2框架针对koa1版本作了兼容处理,中间件函数如果是generator函数的话,会使用koa-convert进行转换为“类async函数”。首先我们必须理解generator和async的区别:async函数会自动执行,而generator每次都要调用next函数才能执行,因此我们需要寻找到一个合适的方法,让next()函数能够一直持续下去即可,这时可以将generator中yield的value指定成为一个Promise对象。下面看看
koa-convert
中的核心代码:首先针对传入的参数mw作校验,如果不是函数则抛异常,如果不是generator函数则直接返回,如果是generator函数则使用co函数进行处理。co的核心代码如下:
由以上代码可以看出,co中作了这样的处理:
以上工作完成后,就形成了一个类async函数。
异步函数的统一错误处理机制
在koa框架中,有两种错误的处理机制,分别为:
中间件捕获是针对中间件做了错误处理响应,如
fnMiddleware(ctx).then(handleResponse).catch(onerror)
,在中间件运行出错时,会出发onerror监听函数。框架捕获是在context.js
中作了相应的处理this.app.emit('error', err, this)
,这里的this.app是对application的引用,当context.js调用onerror时,实际上是触发application实例的error事件 ,因为Application类是继承自EventEmitter类的,因此具备了处理异步事件的能力,可以使用EventEmitter类中对于异步函数的错误处理方法。koa为什么能实现异步函数的统一错误处理?因为async函数返回的是一个Promise对象,如果async函数内部抛出了异常,则会导致Promise对象变为reject状态,异常会被catch的回调函数(onerror)捕获到。如果await后面的Promise对象变为reject状态,reject的参数也可以被catch的回调函数(onerror)捕获到。
委托模式在koa中的应用
delegates库由知名的 TJ 所写,可以帮我们方便快捷地使用设计模式当中的委托模式,即外层暴露的对象将请求委托给内部的其他对象进行处理。
delegates 基本用法就是将内部对象的变量或者函数绑定在暴露在外层的变量上,直接通过 delegates 方法进行如下委托,基本的委托方式包含:
delegates 原理就是__defineGetter__和__defineSetter__。在application.createContext函数中,被创建的context对象会挂载基于request.js实现的request对象和基于response.js实现的response对象。下面2个delegate的作用是让context对象代理request和response的部分属性和方法:
做了以上的处理之后,
context.request
的许多属性都被委托在context上
了,context.response
的许多方法都被委托在context
上了,因此我们不仅可以使用this.ctx.request.xx
、this.ctx.response.xx
取到对应的属性,还可以通过this.ctx.xx
取到this.ctx.request
或this.ctx.response
下挂载的xx
方法。我们在源码中可以看到,response.js和request.js使用的是get set代理,而context.js使用的是delegate代理,为什么呢?因为delegate方法比较单一,只代理属性;但是使用set和get方法还可以加入一些额外的逻辑处理。在context.js中,只需要代理属性即可,使用delegate方法完全可以实现此效果,而在response.js和request.js中是需要处理其他逻辑的,如以下对query作的格式化操作:
The text was updated successfully, but these errors were encountered: