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

中间件执行模块koa-Compose源码分析

时间:2023-04-03 10:09:10 Node.js

原文博客地址,欢迎学习交流:点击预览阅读Koa的源码,相当简洁。当遇到中间件执行模块koa-Compose时,我决定学习一下这个模块的源码。看完本文,您可以了解到:Koa中间件加载下一个参数的来源。中间件控件的执行顺序首先是一段使用Koa启动服务的代码:放在文件app.js中constkoa=require('koa');//require引入koa模块constapp=newkoa();//创建对象app.use(async(ctx,next)=>{console.log('firstmiddleware')next();})app.use(async(ctx,next)=>{console.log('第二个中间件')next();})app.use((ctx,next)=>{console.log('第三个中间件')next();})app.use(ctx=>{console.log('准备响应');ctx.body='hello'})app.listen(3000)以上代码,可以使用nodeapp.js启动,启动后可以访问http://localhost:3000/中浏览器。接入后,启动命令窗口会打印如下值:第一中间件,第二中间件,第三中间件准备响应代码说明:app.use()方法用于将中间件添加到队列。中间件是作为参数传递给app.use()的函数。使用app.use()将函数添加到队列后,当有请求时,会依次触发队列中的函数,即中间件函数会依次一个一个执行,而执行顺序将按照调用app.use()添加的顺序。在每个中间件函数中,都会执行next()函数,也就是说把控制权交给下一个中间件(其实就是调用next函数后,再调用下一个中间件函数,源码后面解释),如果没有调用next()函数,则无法调用next中间件函数,那么队列的执行就会终止,上面代码中的表现就是无法响应客户端的请求。app.use(async(ctx,next)=>{console.log('secondmiddleware')//next();注释后,下一个中间件函数不会执行})内部流程分析内部利用app.use()被添加到数组队列中://在app.use()函数中添加this.middleware.push(fn);//最终的this.middleware是:this.middleware=[fn,fn,fn...]详细可以参考这里Koa的源码使用功能:https://github.com/koajs/koa/blob/master/lib/application.js#L104使用koa-compose模块的compose方法进行合并中间件数组组成一个大的中间件函数constfn=compose(this.middleware);具体可以参考Koa的源码这里https://github.com/koajs/koa/blob/master/lib/application.js#L126请求后,会执行中间件函数fn,然后所有的中间件函数将依次执行。这样片面的描述可能让人看不懂,可以略过,只是让大家了解一下Koa执行中间件的过程。这篇文章主要是分析koa-compose的源码,在分析了整个koa的源码之后会详细讲解,所以最重要的是使用koa-compose模块来控制中间件的执行,所以下面了解这个模块是如何工作的koa-composekoa-compose一个模块可以将多个中间件函数组合成一个大的中间件函数,然后调用这个中间件函数依次执行添加的中间件函数,完成一系列的任务。源码地址:https://github.com/koajs/compose/blob/master/index.js从一段代码开始,创建一个compose.js文件,写入如下代码:constcompose=require('koa-compose');functionone(ctx,next){console.log('first');下一个();//控制权转移到下一个中??间件(实际上可以执行下一个函数),}functiontwo(ctx,next){console.log('second');next();}函数三(ctx,next){console.log('third');next();}//将传入的中间件函数组成的数组队列合并为一个中间件函数constmiddlewares=compose([one,two,three]);//执行中间件函数,并返回Promise对象middlewares().then(function(){console.log('Queueexecutioncompleted');})可以用nodecompose.js运行这个文件,命令行窗口打印出来:第一、二、三队列执行完毕.这里中间件的关键点是,是组合功能。compose函数的源码虽然很简洁,但是要看懂还是费了一番功夫。以下为源码分析:'usestrict'/***Exposecompositor.*///Exposecompose函数module.exports=compose/***Compose`middleware`返回*一个完全有效的中间件,其中包含*所有传递的中间件。**@param{Array}middleware*@return{Function}*@apipublic*///compose函数需要传入一个数组queue[fn,fn,fn,fn]functioncompose(middleware){//Ifpassed如果输入不是数组,会抛出错误不是函数,会抛出错误{Object}context*@return{Promise}*@apipublic*///compose函数调用后,返回以下匿名函数//匿名函数接收两个参数,第一个随便传入,它根据使用场景决定//第二个参数next是第一次调用其实是未定义的,因为第一次调用不需要传入next参数//这个匿名函数返回一个promise返回函数(context,next){//最后调用的中间件#//初始下标为-1letindex=-1returndispatch(0)functiondispatch(i){//如果传入的i为负数且<=-1,则返回一个Promise.reject,并带有错误信息//所以执行next两次会报这个错误,状态为rejected,这是为了保证next在a中只调用一次middlewareif(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))//执行next后,index值会发生变化index=i//根据subscriptletfn=middleware[i]//next是这个内部的一个局部变量,值为undefined//当i已经是数组长度时,表示中间件函数全部执行完,执行完后,setfntoundefined//问题:本来middleware[i]如果i是length,得到的值已经是undefined,为什么还要把fn再设置成undefined?if(i===middleware.length)fn=next//如果中间件已经遍历到末尾。所以。此时returnPromise.resolve()返回一个状态为成功的promise//在切面调用之后thenif(!fn)returnPromise.resolve()//trycatch保证在承诺。//调用后,仍然返回一个成功状态的Promise对象//用Promise包裹中间件,方便await调用//调用中间件函数,传入context(根据不同的场景可以传入不同的值),KOa中传入的是ctx)//第二个参数是一个next函数,可以在中间件函数中调用//调用next函数后,递归调用dispatch函数执行下一个中间件函数//的next函数在中间件中函数调用后返回的是一个promise对象//看完不得不佩服作者的高明。try{returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){returnPromise.reject(err)}}}}补充说明:根据出处上面代码分析,中间件函数中不能调用两次next(),否则会报错functionone(ctx,next){console.log('first');下一个();next();}报错:next()calledmultipletimesnext()调用后返回的是一个Promise对象,可以调用then函数functiontwo(ctx,next){console.log('second');next().then(function(){console.log('第二次调用then之后')});}中间件函数可以是async/await函数,任何异步处理都可以写在函数内部,并且得到文件函数的处理结果后可以执行下一步的中间步骤。创建一个名为test-async.js的文件并编写以下代码:constcompose=require('koa-compose');//getdataconstgetData=()=>newPromise((resolve,reject)=>{setTimeout(()=>resolve('Getdata'),2000);});asyncfunctionone(ctx,next){console.log('第一个,等待两秒再进行下一个中间件');//模拟异步读取数据库数据awaitgetData()//等到获取到数据,继续执行下一个中间件next()}functiontwo(ctx,next){console.log('second');next()}函数三(ctx,next){console.log('third');next();}constmiddlewares=compose([one,two,three]);middlewares().then(function(){console.log('queueexecutioncompleted');})你可以使用nodetest-async。js运行这个文件,命令行窗口打印出来:first,等待两秒再进行下一个中间件secondandthird第二次调用then后,执行队列。在上面的打印输出过程中,第一个中间件执行完后,里面会有一个异步操作。使用async/await后,可以获得与同步操作相同的体验。这一步可能是读取数据库数据或读取文件。读取数据后调用next()执行下一个中间件。这里模拟等待2秒,然后执行下一个中间件。更多关于async/await的参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_functionhttps://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await执行顺序调用next后,执行顺序会比较混乱。创建一个名为text-next.js的文件并编写以下代码:constkoa=require('koa');constapp=newkoa();app.use((ctx,next)=>{console.log('第一个中间件函数')next();console.log('第一个中间件函数之后');})app.use(async(ctx,next)=>{console.log('第二个中间件函数')next();console.log('第二个中间件函数之后next');})app.use(ctx=>{console.log('response');ctx.body='hello'})app.listen(3000)上面的代码可以使用nodetext-next.js启动,启动后可以在浏览器中显示访问http://localhost:3000/后,命令窗口会打印如下值:thefirstmiddlewarefunction,secondmiddlewarefunctionrespondstothesecondmiddlewarefunctionnext,andfirstmiddlewarefunctionnext对piecefunctionnext之后的这个顺序深有疑问,为什么会这样?当中间件调用next()时,该函数暂停并将控制权传递给下一个定义的中间件。当下游不再有中间件执行时,堆栈展开,每个中间件恢复执行其上游行为。流程是这样的:先执行第一个中间件函数,打印出'第一个中间件函数'调用next,不继续执行第二个中间件函数,打印出'第二个中间件函数'函数'调用next,并且不会继续执行最后一个中间件函数,并打印出'response'...最后一个中间件函数执行完后,之前的中间件函数收回控制权,继续执行,并打印出'response'...之后thesecondmiddlewarefunctionisnext'第二个中间件函数执行后,前面的中间件函数收回控制权继续执行,打印出'afterthefirstmiddlewarefunctionnext'借张图直观说明:具体看别人如何了解next的顺序:https://segmentfault.com/q/1010000011033764最近在看Koa的源码。以上是我个人的理解。如有偏差,请指正,学习,谢谢。参考:https://koa.bootcss.com/https://cnodejs.org/topic/58fd8ec7523b9d0956dad945