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

Koa源码分析

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

上一篇写了如何阅读Koa的源码,简单的通过了Koa的源码,但是作为一个人并没有得出具体的结论,中间件的运行原理也不是很清楚,这里我们通过Koa源码再仔细看,照着例子先过一遍例子constKoa=require('koa');constapp=newKoa();app.use(asyncctx=>{ctx.body='HelloWorld';});app.listen(3000);启动一个web服务,来个HelloWorld,作为http模块的重新封装,我们慢慢挖掘它是如何封装的(我会删除不相关的代码)。第一个是listen:listen(...args){constserver=http.createServer(this.callback());返回server.listen(...args);}我们都知道的http模块无非就是http.createServer(fn).listen(port),其中fn携带了req,res。根据上面的封装,我们可以确定this.callback一定是带着request和response的。那我们再看看this.callback。callback(){constfn=compose(this.middleware);consthandleRequest=(req,res)=>{constctx=this.createContext(req,res);返回this.handleRequest(ctx,fn);};返回句柄请求;}果然callback返回的函数是with关注req和res,那我继续往下看handleRequest到底经历了什么。ctx老大出现了。我们在使用koa的时候,所有的请求响应都挂在ctx上。好像ctx是通过createContext创建的,那继续看createContext:createContext(req,res){constcontext=Object.c重新创建(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={};返回上下文;变量和上下文挂钩,代码很简单,但是因为涉及到request和response,所以需要简单看一下request.js和response.js:module.exports={getheader(){return这个.req.headers;},//..moreitems}获取变量很简单,没什么好说的,再回到之前的回调部分,ctx的创建ok,然后调用返回this.handleReques,没什么好说的,继续阅读:handleRequest(ctx,fnMiddleware){constres=ctx.res;res.statusCode=404;constonerror=err=>ctx.onerror(错误);consthandleResponse=()=>respond(ctx);onFinished(res,onerror);返回fnMiddleware(ctx).then(handleResponse).catch(onerror);这部分稍微复杂一点,从上面可以看出fnMiddleware就是我们取出来的中间件,然后我们将ctx传递给中间件执行,这和我们平时的用法有点类似。至此,重点在于:middlewaremiddleware在探究中间件原理之前,不妨先看看中间件是如何使用的,这里举个简单的例子:constKoa=require('koa')constapp=newKoa()app.use(asyncfunctionm1(ctx,nex){console.log('m1')awaitnext()console.log('m2end')})app.use(asyncfunctionm2(ctx,nex){console.log('m2')awaitnext()console.log('m2end')})app.use(asyncfunctionm3(ctx,nex){console.log('m3')ctx.body='HelloWorld'})上面的结果已经很清楚了,但是我们不妨形象化一下:也停下让m3先走m2:...(委屈)m3:outputm3,上面注意远了路返回m2:outputm2endm1注意我要returnm1:outputm1endrespond:ctx。身体是你好世界就是骗用户回去。看,ctx.body并不代表立即响应,它只是我们后面要用到的一个变量,也就是说我们的ctx会经过所有的中间件才响应。这里先不说await神奇的暂停效果,我们只需要能够这样使用就可以了。那么我们的中间件是如何实现的呢?让我们看看compose.js:functioncompose(middleware){/***@param{Object}context*@return{Promise}*@apipublic*/returnfunction(context,next){letindex=-1returndispatch(0)functiondispatch(i){index=i让fn=middleware[i]if(!fn)returnPromise.resolve()returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}}}看了我之前的文章就可以知道,这其实就是一个递归。但是不同于connect的递归,这是Promise。我们都知道await和Promise放在一起味道更好。接下来是重点。在我们接下来调用await之后,程序必须等待Promise被执行。让我们简化中间件模型:(m3)ctx.body='xxx'})console.log(m2end)})console.log(m1end)})清楚了吗?作为应用层的东西,我们不需要想一想async/await是怎么实现的,只需要了解它实现了什么样的效果就可以了。还是要佩服大神tj。有什么问题可以互相交流。