当前位置: 首页 > Web前端 > JavaScript

【节点】深入理解Koa的洋葱模型

时间:2023-03-27 11:45:08 JavaScript

本文将讲解koa的洋葱模型,为什么要使用洋葱模型,以及它的原理实现。掌握洋葱模型对于理解koa非常重要。希望这篇文章对你有所帮助~什么是洋葱模型?先看一个democonstKoa=require('koa');constapp=newKoa();//middleware1app.use((ctx,next)=>{console.log(1);next();console.log(2);});//中间件2app.use((ctx,next)=>{console.log(3);next();console.log(4);});app.listen(8000,'0.0.0.0',()=>{console.log(`服务器正在启动`);});输出结果为:1342在koa中,通过next()方法将中间件分成两部分。next()方法的上半部分会先被执行,下半部分会在后续的所有中间件执行完毕后执行。从下图可以直观的看出:在洋葱模型中,每一层都相当于一个中间件,用来处理具体的功能,比如错误处理,会话处理等等。处理顺序首先是next()之前的request(Request,从外层到内层),然后执行next()函数,最后是next()之后的response(Response,从内层到外层层),也就是说,每个中间项都有两次处理机会。为什么Koa使用洋葱模型?如果不是洋葱模型,我们的中间件依赖于其他中间件的逻辑,怎么办?例如,我们需要知道请求或操作数据库需要多长时间,我们想获取有关其他中间件的信息。在koa中,我们可以结合onion模型使用asyncawait。app.use(async(ctx,next)=>{conststart=newDate();awaitnext();constdelta=newDate()-start;console.log(`请求耗时:${delta}MS`);console.log('获取上次请求的结果:',ctx.state.baiduHTML);})app.use(async(ctx,next)=>{//处理db或者发起HTTP请求ctx.state.baiduHTML=awaitaxios.get('http://baidu.com');})而如果没有洋葱模型,这个是做不到的。深入Koa洋葱模型下面结合文章开头的demo来分析一下koa的内部实现。constKoa=require('koa');//Applicationsconstapp=newKoa();//中间件1app.use((ctx,next)=>{console.log(1);next();console.log(2);});//中间件2app.use((ctx,next)=>{console.log(3);next();console.log(4);});app.listen(9000,'0.0.0.0',()=>{console.log(`服务器正在启动`);});use方法use方法就是做一件事,维护中间件中间件数组use(fn){//...//维护中间件数组——middlewarethis.middleware.push(fn);归还这个;}listen方法和回调方法app.listen方法执行时,实际上是通过Node.js原生http模块的createServer方法创建一个回调为回调方法的服务。在回调方法中,有我们今天关键的compose函数,它的返回是一个Promise函数。listen(...args){debug('listen');//节点http创建一个服务constserver=http.createServer(this.callback());返回server.listen(...args);}callback(){//返回值是一个函数constfn=compose(this.middleware);consthandleRequest=(req,res)=>{//创建ctx上下文constctx=this.createContext(req,res);返回这个.handleRequest(ctx,fn);};返回句柄请求;}handleRequest会执行compose函数中返回的Promise函数并返回结果。handleRequest(ctx,fnMiddleware){constres=ctx.res;res.statusCode=404;constonerror=err=>ctx.onerror(err);consthandleResponse=()=>respond(ctx);onFinished(res,onerror);//执行compose返回的函数并返回结果returnfnMiddleware(ctx).then(handleResponse).catch(onerror);}koa-composecompose函数是指koa-compose库。它的实现如下:functioncompose(middleware){//...returnfunction(context,next){//最后调用的中间件#letindex=-1//开始时传入0,后面会递增returndispatch(0)functiondispatch(i){//如果没有递增,说明已经执行了多次if(i<=index)returnPromise.reject(newError('next()calledmultipletimes'))index=i//获取当前中间件letfn=middleware[i]if(i===middleware.length)fn=next//当fn为空时,next()后面的代码if(!fn)returnPromise.resolve()try{//执行中间件,注意这两个参数,都是中间件传递的参数,第一个是上下文,第二个是下一个函数//也就是说,executeNext是调用dispatch函数的时候returnPromise.resolve(fn(context,dispatch.bind(null,i+1)));}catch(err){returnPromise.reject(err)}}}}code很简单,我们来看看具体的执行过程:我们第一次执行的时候,调用了dispatch(0),此时i为0,fn为第一个中间件函数。而执行中间件,注意这两个参数,都是中间件传递的参数,第一个是上下文,第二个是next函数。也就是说,中间件接下来执行的时候,也是调用dispatch函数的时候,这就是为什么下一个中间件会在执行下一个逻辑的时候执行:returnPromise.resolve(fn(context,dispatch.bind(空,我+1)));第二次和第三次执行dispatch时,和第一次一样,分别开始执行第二个和第三个中间件,执行next()时开始执行下一个中间件。当执行第三个中间件时,执行next()时,传入dispatch函数的参数为??3,fn为undefined。这时候if(!fn)returnPromise.resolve()就会被执行。这时候会执行第三个中间件next()之后的代码,然后是第二个,最后是第一个,这样就形成了洋葱模型。流程如下:简易版compose模仿koa的逻辑,我们可以写一个简易版的compose。方便大家理解:constmiddleware=[]letmw1=asyncfunction(ctx,next){console.log("next之前,第一个中间件")awaitnext()console.log("next之后,第一个中间件")}letmw2=asyncfunction(ctx,next){console.log("下一个之前,第二个中间件")awaitnext()console.log("下一个之后,第二个中间件")}letmw3=asyncfunction(ctx,next){console.log("第三个中间件,没有next")}functionuse(mw){middleware.push(mw);}functioncompose(middleware){return(ctx,next)=>{返回调度(0);函数调度(i){constfn=middleware[i];如果(!fn)返回;返回fn(ctx,dispatch.bind(null,i+1));}}}use(mw1);use(mw2);use(mw3);constfn=compose(middleware);fn();总结一下Koa的洋葱模型是指next()函数作为切分点,首先执行从外到内的Request逻辑,再从内到外执行Response的逻辑。通过洋葱模型,多个中间件之间的通信变得更加可行和简单。它的实现原理不是很复杂,主要是compose方法。参考浅谈koa的洋葱模型如何更好的理解中间件和洋葱模型