当前位置: 首页 > 科技观察

你需要掌握的Koa洋葱模型和中间件

时间:2023-03-19 22:11:03 科技观察

大家好,我是前端西瓜哥。Koa是一个nodejs框架,常用于编写web后端服务。是Express框架原班人马开发的新一代Web框架,使用async/await优雅地处理无处不在的异步逻辑。我们常说Koa其实就是洋葱模型。今天,我们就来深入了解一下Koa的洋葱模型是什么。什么是洋葱模型在此之前,我们先简单了解一下Koa是如何使用的。在Koa中,我们通过app.use方法注册中间件。可以注册多个中间件,它们的执行顺序与注册时机有关,先注册的先执行。所谓中间件就是接受Koa提供的两个参数的函数:ctx上下文对象;下一个功能。ctx上有各种参数,比如request对象request,response对象response。调用下一个函数将执行下一个中间件。如果不调用next函数,则不会执行next中间件。让我们看一个例子:constKoa=require('koa');constapp=newKoa();//中间件1:记录请求耗时app.use(async(ctx,next)=>{console.log('Middleware1');conststart=newDate().getTime();awaitnext();constt=newDate().getTime()-start;console.log('Therequesttooktime',t+'ms');})//中间件2:getdataapp.use(async(ctx,next)=>{console.log('middleware2')constdata=awaitgetData();ctx.body={data};})//模拟从数据库中获取数据,耗时1sconstgetData=async()=>{awaitnewPromise((resolve)=>{setTimeout((){resolve();},1000);})return'HelloWorld!';}app.listen(3005);请求时,服务端的日志是这样的:中间件中间件2请求从中间件1到休眠需要1005ms,代码逻辑可以分为三部分:先执行next()。然后在next()之后执行中间件2的所有代码。最后执行next()后面的代码。该特性先执行当前中间件的前半部分逻辑,处理完后续中间件后,再继续执行当前中间件的后半部分。这使我们可以像处理洋葱一样处理从外部到内部的请求。对象,然后由内向外处理响应对象,因此称为洋葱模型。洋葱模式本质上是设计模式中责任链模式的一种变体。责任链模式是指请求和响应的解耦,让多个处理对象有机会相应地处理请求。例如,处理对象A先处理数据,然后将处理后的数据传递给处理对象B,以此类推,形成一条链。链上不同的处理对象,各负其责。A->B->C与经典的责任链模型相比,洋葱模型可以将一个处理器分为两部分,它们在不同的时间触发但具有相同的上下文,这在某些情况下非常有用,例如打印刚才提到的单个请求所需的时间。A1->B1->C->B2->A2Koa源码实现Koa是一个非常轻量级的库,源码分析起来比较容易,那么我们来看看它的洋葱模型,也就是中间件模型的实现吧.但是因为实现了大量的闭包,看起来还是很容易眼花缭乱。首先,newKoa()创建的app对象有一个成员属性middleware,其初始值为空数组。这个中间件就是保存中间件函数的地方。每当我们调用app.use(fn)时,Koa都会将中间件功能添加到中间件中。use(fn){this.middleware.push(fn)returnthis}最后我们调用app.listen(port),这个API将启动http服务器。listen(...args){constserver=http.createServer(this.callback())returnserver.listen(...args)}this.callback是返回一个封装好的函数给nodejs原生的http.createServer是用过的。回调实现为:callback(){constfn=this.compose(this.middleware)if(!this.listenerCount('error'))this.on('error',this.onerror)consthandleRequest=(req,res)=>{constctx=this.createContext(req,res)returnthis.handleRequest(ctx,fn)}returnhandleRequest}再看看this.compose方法,它组合了多个中间件函数,因此它们可以被相应地调用。这个compose被提取到一个名为koa-compose的npm包中,它包含的代码非常少。我提取了核心代码:最后一个中间件if(!fn)returnPromise.resolve()returnPromise.resolve(fn(context,dispatch.bind(null,i+1)))}returndispatch(0)}}compose函数就是组装中间件function,首先返回第一个中间件函数的包,其类型签名为()=>Promise。当这个封装好的函数执行时,会执行原来的中间件函数,得到下一个中??间件函数的封装,也就是next。我们回到回调方法,这里this.handleRequest的实现是:handleRequest(ctx,fnMiddleware){constres=ctx.resres.statusCode=404constonerror=errctx.onerror(err)consthandleResponse=()respond(ctx)onFinished(res,onerror)returnfnMiddleware(ctx).then(handleResponse).catch(onerror)}this.handleRequest调用compose返回的第一个中间件。Express是洋葱模型吗?Express发布的时候,ES6还没有出来,Promises也不能用,更别提ES7的async/await了。因此,当时Express无法实现这种异步洋葱模型。那时候做异步的唯一方法就是使用回调的方式。总的来说,Express可以看作是一个只支持同步的洋葱模型,因为它在实现中并没有处理next是async的情况,这是历史原因造成的。当Express调用res.send时,结束数据处理,将响应数据返回给客户端。Res.rend不能在一个请求中调用多次。Koa将内容添加到ctx.response中,等待中间件完成后再返回数据。最后的洋葱模型是把数据依次传递给多个中间件,让它们处理和传递,利用函数递归的特性,这样我们就可以在一个中间件中执行前半部分的逻辑,然后执行所有的中间件完成中间件的完整逻辑后,转身继续??执行中间件的后半部分。比起进入下一个中间件不回来,这种实现方式可以让我们的代码更加灵活,在某些场景下还是很有用的。