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

【源码学习----koa】koa中间件核心(koa-compose)源码解读分析

时间:2023-04-03 19:28:50 Node.js

最近经常用koa做服务端开发,迷上了koa的洋葱模型。我觉得这个东西很好用。而koa主要是精简,集成的东西不多,所有的东西都需要按需加载,这个太对我胃口了哈哈哈哈。express的中间件和express的中间件相比,express的中间件采用的是串联,一个接一个的像糖葫芦,而koa采用的是V型结构(洋葱模型),这会为我们的中间件提供更灵活的处理方式。基于对洋葱模型的狂热,所以对koa的洋葱模型有了更深入的了解。koa1和koa2的中间件都是基于koa-compose编写的。这个V型结构的实现来自于koa-compose。先附上源码:functioncompose(middleware){//参数middleware是一个中间件数组,存放的是我们用app.use()一个一个拼接的中间件//判断中间件列表是否为数组,如果不是,则抛出类型错误for(constfnofmiddleware){if(typeoffn!=='function')thrownewTypeError('中间件必须由函数组成!')}/**1.@param{Object}context2.@return{Promise}3.@apipublic*/returnfunction(context,next){//这里的next指的是洋葱模型的中心函数//context是一个配置对象,存储了一些配置。当然也可以使用context传递一些参数给Next中间件传递//最后调用的中间件#letindex=-1//index是记录执行的中间件索引returndispatch(0)//执行第一个中间件和通过第一个中间件递归调用下一个Middlewarefunctiondispatch(i){//这里是保证同一个中间件中的一个next()不会被多次调用//当next()函数被调用两次时,i会小于index,然后抛出Errorif(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))index=iletfn=middleware[i]//取出要执行的中间件Itemif(i===middleware.length)fn=next//如果i等于中间件的长度,即到洋葱模型的中心(最后一个中间件)if(!fn)returnPromise.resolve()//如果中间件为空,即直接解析1)));}catch(err){returnPromise.reject(err)}}}}看到这里,如果你能看懂下面的内容,那么下面的就不用看了,如果还不明白,继续往下看下面,详细分析首先,我们使用app.use()添加一个中间件。在koa的源码中,app.use()方法是将一个中间件push到middleware的middleware列表中。源码里是这样写的(这个不用分析就比较简单):use(fn){if(typeoffn!=='function')thrownewTypeError('middlewaremustbeafunction!');if(isGeneratorFunction(fn)){deprecate('对生成器的支持将在v3中删除。'+'请参阅文档以获取有关如何转换旧中间件的示例'+'https://github.com/koajs/koa/blob/master/docs/migration.md');fn=转换(fn);}debug('使用%s',fn._name||fn.name||'-');这个.middleware.push(fn);归还这个;}compose方法传入一个中间件列表中间件。这个列表是我们使用use()添加的方法列表。首先会判断列表是否为数组,中间件是否为方法。如果没有,将直接抛出类型错误。compose返回的是一个函数,其中一个闭包用于缓存中间件列表,然后这个函数接收两个参数,第一个参数是context,它是一个存储配置信息的对象。第二个参数是一个next方法,也是洋葱模型的中心或者V型模型的拐点。创建一个index变量保存执行过的中间件索引,然后从第一个中间件开始递归执行。letindex=-1returndispatch(0)dispatch方法是执行中间件,先判断index,如果i小于index,说明下一个函数已经在同一个中间件执行过两次或多次,如果i>indexthen表示中间件还没有执行,所以记录中间件的详细信息if(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))index=i取out中间件,如果i等于中间件的长图,说明执行到了洋葱模型的中心,然后最后一个中间件,如果中间件为空,那么resovle直接让fn=middleware[i]if(i===middleware.length){fn=next}if(!fn){returnPromise.resolve()}最精彩的部分来了,也有点绕。首先,为什么return是一个Promise对象(Promise.resolve也是一个promise对象),因为我们在awaitnext()的时候,await是在等待并执行一个async函数的完成,而async会通过返回一个promise对象default,所以这里返回的是一个promise对象。我们在每个中间的awaitmext()next()指的是下一个中间件,即fn(context,functionnext(){returndispatch(i+1)}),所以我们在上一个await也就是dispatch(i+1)执行完毕,dispatch返回的是Promise.resolve(fn(context,functionnext(){xxxx}))。从这个角度来看,虽然一开始只执行了dispatch(0),但是决定了这个函数形成了一个执行链。以三个中间件的执行为例。dispatch(0)执行后会形成:Promise.resolve(//第一个中间件function(context,next){//这里的下一个第二个中间件是dispatch(1)//awaitnext(middleware1)上的代码)awaitPromise.resolve(//第二个中间件function(context,next){//这里next的第二个中间件是dispatch(2)//awaitnext(middleware2)上的代码awaitPromise.resolve(//第三个middlewarefunction(context,next){//这里的第二个中间件是dispatch(3)//awaitnext上面的代码(middleware3)awaitPromise.resolve()//awaitnext(middleware3)下的代码})//代码underawaitnext(middleware2)})//codeunderawaitnext(Middleware2)})首先执行上面的await代码,然后等待最后一个中间件resolve一个一个传上去,这样就形成了一个洋葱模型。最后附上测试代码:asyncfunctiontest1(ctx,next){console.log('Middleware1up');等待下一个();console.log('中间件1down');};asyncfunctiontest2(ctx,next){console.log('Middleware2up');等待下一个();console.log('中间件2down');};asyncfunctiontest3(ctx,next){console.log('中间件3up');等待下一个();console.log('中间件3down');};让中间件=[test1,test2,test3];让cp=组合(中间件);cp('ctx',function(){console.log('center');});OK,到这里koa2的中间件核心(koa-compose)已经解析完毕。初读时,也是绕了好久。Readmore多分析几遍,一步步磨平。koa1的中间件过几天就可以加上了。koa1是基于generator的,源码相对koa2要简单一些。最近在看koa2源码,有空会继续更新一些koa的源码分析。前端小学生(应届毕业生),如有错误或其他想法。欢迎指正交流~