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

koa的洋葱模型实现分析

时间:2023-04-03 16:06:02 Node.js

前言Koa被认为是第二代节点web框架。其最大的特点是独特的中间件流程控制,是典型的洋葱模型。koa和koa2中间件的思想是一样的,只是实现方式不同。node7.6之后,koa2可以直接使用async/await代替generator来使用中间件。本文以最后一种情况为例。下面两张洋葱模型的图是在网上找的,很清楚地展示了请求是如何通过中间件产生响应的。这种模式下开发和使用中间件非常方便。看一个koa2demo:constKoa=require('koa');constapp=newKoa();constPORT=3000;//#1app.use(async(ctx,next)=>{console.log(1)awaitnext();console.log(1)});//#2app.use(async(ctx,next)=>{console.log(2)awaitnext();console.log(2)})app.use(async(ctx,next)=>{console.log(3)})app.listen(PORT);console.log(`http://localhost:${PORT}`);访问http://localhost:3000,控制台打印:12321怎么样,是不是有点感觉。当程序运行到awaitnext()时,会暂停当前程序,进入下一个中间件。处理完后会返回继续处理。也就是说,当一个请求进来的时候,#1会被第一个和最后一个传递,#2会被第二个和倒数第二个传递,以此类推。koa的实现有几个最重要的点。context的保存和投递中间件的管理以及next的实现查看源码,发现app.listen中使用this.callback()生成回调函数listen(...args){debug('listen');constserver=http.createServer(this.callback());returnserver.listen(...args);}再看this.callbackcallback(){constfn=compose(this.middleware);if(!this.listeners('error').length)this.on('error',this.onerror);consthandleRequest=(req,res)=>{constctx=this.createContext(req,res);返回this.handleRequest(ctx,fn);};returnhandleRequest;}这里使用compose处理this.middleware,创建ctx并赋值给createContext的返回值,最后返回handleRequest。this.middleware看起来应该是中间件的集合。我检查了代码,果然:this.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||'-');这。中间件.push(fn);returnthis;}不管兼容性和判断,这段代码只做了一件事:use(fn){this.middleware.push(fn);returnthis;}事实证明,当我们的应用程序。使用时,只需将方法存储在一个数组中即可。那么什么是组合?追源码可以看到compose来自koa-compose模块,代码不多:(去掉了一些不影响主逻辑的判断)functioncompose(middleware){returnfunction(context,next){//最后调用的中间件#letindex=-1returndispatch(0)functiondispatch(i){if(i<=index)returnPromise.reject(newError('next()被多次调用'))index=iletfn=middleware[i]if(i===middleware.length)fn=nextif(!fn)returnPromise.解决()尝试{返回承诺。resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){returnPromise.reject(err)}}}}关键是dispatch函数,会遍历整个中间件,然后将上下文和dispatch(i+1)传递给中间件中的方法。returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))这段代码巧妙地实现了两点:1.将`context`一路传递给中间件2.使用下一个中间件``middleware`中的fn作为以后`next`的返回值也是洋葱模型实现的核心。往下看代码,其实并没有多少花样。createContext和handleRequest所做的其实就是将ctx绑定到中间件上,也就是第一次调用compose返回值的地方。createContext(req,res){constcontext=Object.create(this.context);constrequest=context.request=Object.create(this.request);constresponse=context.response=Object.create(this.response);context.app=request.app=response.app=this;context.req=request.req=response.req=req;context.res=request.res=response.res=res;request.ctx=response.ctx=上下文;请求.响应=响应;response.request=请求;context.originalUrl=request.originalUrl=req.url;context.cookies=newCookies(req,res,{keys:this.keys,secure:request.secure});request.ip=request.ips[0]||req.socket.remoteAddress||'';context.accept=request.accept=接受(请求);context.state={};返回上下文;}handleRequest(ctx,fnMiddleware){constres=ctx.res;res.statusCode=404;constonerror=err=>ctx.onerror(err);consthandleResponse=()=>respond(ctx);onFinished(res,onerror);返回fnMiddleware(ctx).then(handleResponse).catch(onerror);}??