当前位置: 首页 > 后端技术 > Node.js

koa中间件机制详解

时间:2023-04-03 16:42:56 Node.js

koa是express原班人马打造的一个体积更小、表现力更强、更健壮的web框架。在我眼里,koa确实比express轻很多。Koa对我来说更像是一个中间件框架。Koa只是一个基本的架子。当需要使用相应的功能时,使用相应的中间件来实现就好了,比如路由系统等等。更好的一点是express是基于回调的。至于回调有多烂,大家可以自行搜索。koa1基于co库,所以koa1使用Generator代替callback,koa2使用async/await,因为node支持async/await。关于async和co库等,可以参考我之前写的一篇文章(理解async)。Koa可以说是各种中间件的架子。再来看看Koa对中间件部分的实现:koa1的中间件主要是Generator实现的。一般来说,koa1的一个中间件大概长这样:app.use(function*(next){console.log(1);yieldnext;console.log(5);});app.use(function*(next){console.log(2);yieldnext;console.log(4);});app.use(function*(){console.log(3);});这样的输出会是1,2,3,4,5,koa中间件的实现主要依赖于koa-compose:functioncompose(middleware){returnfunction*(next){if(!next)next=noop();vari=middleware.length;//组合中间件while(i--){next=middleware[i].call(this,next);}返回收益*下一个;}}function*noop(){}源码很简单,实现的功能是把所有的中间件串联起来,先把一个noop传给倒数第二个中间件作为next,然后把排好序的倒数第二个中间件作为next传过去到倒数第二个中间件,最后next是排序后的第一个中间件。说的比较复杂,我们画一张图:效果和上图一样,和redux需要达到的目标类似。只要遇到yieldnext,就会执行下一个中间件。使用co库很容易串联这个过程。简单模拟下,中间件的完整实现:??constmiddlewares=[];constgetTestMiddWare=(loggerA,loggerB)=>{返回函数*(next){console.log(loggerA);接下来屈服;console.log(loggerB);}};constmid1=getTestMiddWare(1,4),mid2=getTestMiddWare(2,3);constgetData=newPromise((resolve,reject)=>{setTimeout(()=>resolve('数据已取回'),1000);});function*response(next){//模拟异步读取数据库数据常量数据=yieldgetData;console.log(data);}middlewares.push(mid1,mid2,response);//简单模拟co库函数co(gen){constctx=this,args=Array.prototype.slice.call(arguments,1);returnnewPromise((reslove,reject)=>{if(typeofgen==='function')gen=gen.apply(ctx,args);if(!gen||typeofgen.next!=='function')returnresolve(gen);constbaseHandle=handle=>res=>{letret;试试{ret=gen[handle](res);}抓住(e){拒绝(e);}下一个(退);};constonFulfilled=baseHandle('next'),onRejected=baseHandle('throw');onFulfilled();functionnext(ret){if(ret.done)returnreslove(ret.value);//将yield的返回值转换为Proimseletvalue=null;if(typeofret.value.then!=='function'){value=co(ret.value);}else{value=ret.value;}if(value)returnvalue.then(onFulfilled,onRejected);returnonRejected(newTypeError('yieldtypeerror'));}});}//调用方法constgen=compose(middlewares);co(gen);koa2的中间件,随着node对async/await的支持,好像不再需要使用co的工具库了,直接用原来的就可以了,所以koa也做了改动,看看现在的koa-compose:functioncompose(middleware){//参数检查返回函数(context,next){//最后调用middleware#letindex=-1returndispatch(0)functiondispatch(i){if(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))index=iletfn=middleware[i]//最后一个中间件的调用if(i===middleware.length)fn=nextif(!fn)returnPromise.resolve()//用Promise包裹中间件,方便await调用try{returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){returnPromise.reject(err)}}}}koa-compose使用了Promise,koa2的中间件其中一个参数从一个变成了两个,执行下一个的中间件使用了awaitnext()。要达到和上面示例代码一样的效果,需要改变中间件的写法:constmiddlewares=[];constgetTestMiddWare=(loggerA,loggerB)=>async(ctx,next)=>{console.log(记录器A);等待下一个();console.log(loggerB);};constmid1=getTestMiddWare(1,4),mid2=getTestMiddWare(2,3);constresponse=async()=>{//模拟异步读取数据库数据constdata=awaitgetData();控制台.log(data);};constgetData=()=>newPromise((resolve,reject)=>{setTimeout(()=>resolve('datahasbeenretrieved'),1000);});middlewares.push(mid1,mid2);//调用方法compose(middlewares)(null,response);如何做到兼容可以看出koa1和koa2在中间件的实现上还是有很多区别的,而koa1的中间件直接用koa2肯定会出错。如何兼容这两个版本也成为了一个问题。koa团队写了一个包让koa1的中间件可以用在koa2中,叫做koa-convert,先来看看这个包是怎么用的:function*mid3(next){console.log(2,'koa1middleware');接下来屈服;console.log(3,'koa1middleware');}convert.compose(mid3)来看看这个包实现的思路:每个koa1中间件convert.compose=function(arr){if(!Array.isArray(arr)){arr=Array.from(arguments)}returncompose(arr.map(convert))}//关键在于convertconstconvert=mw=>(ctx,next)=>{//配合co库,返回一个Promise,同时执行yieldreturnco.call(ctx,mw.call(ctx,createGenerator(next)));};function*createGenerator(next){/*next在koa-comomse:functionnext(){returndispatch(i+1)}*/returnyieldnext()//完成执行koa1中间件,回到使用await执行koa2中间件的正轨}个人感觉koa-convert的思路是为Generator封装一层Promise,让前面的中间件可以通过awaitnext()调用.对于Generator的执行,使用co库来达到兼容的目的