深入node.js koa原理,实现koa
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入node.js koa原理,实现koa相关的知识,希望对你有一定的参考价值。
express 和koa 的对比
- express源码是基于es5写的,koa是基于es6写的。
- express比较全,内置了很多功能,koa内部核心非常小巧,需要通过扩展的插件来进行扩展
- express 和koa都hi可以自己实现mvc功能
- express处理异步都是回调函数,而koa处理异步都是async + await
koa
koa的ctx上下文提供很多比如使用http模块中的req通过解析才能获取的pathname,koa中直接通过ctx.path获取。因为底层还是http,还是通过封装http模块的一些东西,让我们可以更加简便的的使用,而不用像使用http模块那么费劲。
实现一个koa
目录保持一致。主要实现use listen方法
底层还是基于http模块,
listen方法还是创建一个server。
use方法存放中间件,相当于订阅。
每次请求来了之后就处理请求,然后处理订阅的中间件,有点像发布订阅的模式。
- 这里的createContext是用来创建ctx的。这里做了两层隔离。最开始是constructor的隔离,避免多个new Koa的时候,他们改变的context是一样的。
第二层就是每次请求的时候,都会生成全新的ctx,这里也需要做隔离处理,避免不同请求的ctx是一样的,这样隔离了,每个实例的ctx都是全新的对象,每个请求的ctx也是全新的对象。源码中也是这么处理的。
- 接着实现koa的上下文ctx。
先将原生的req和res挂载到ctx的requests和response上去。此时就可以通过ctx.req做原生的处理。接着处理request,如果从ctx.request.xx获取值,
- request里面采用了Object.defineProperty的简单写法,比如get url()就是
Object.defineProperty(request, ‘url’, get())这样。然后每次通过ctx.request.xx获取值的时候,会通过原型链找到这里,调用url的get方法,此时里面的this指向的是ctx.request,而刚刚把原生req挂载到了ctx.request上面了,所以就可以通过this.req来获取一些值,甚至可以做一些处理,如path,query等等,这也是req挂载到ctx.reqeust上的原因。 - 这时候就可以通过ctx.request.query获取值了,然后ctx想获取值,就需要在座一层代理,将ctx.xx代理到ctx.request.xx上。
正常还是这么做就行了,但是太多个写起来很费劲,源码是采用了Object.prototype._ _defineGetter__来完成的。
我们这里简单模拟一下,代理context,调用 _ _defineGetter__方法,然后对每个Key做代理,比如ctx.url,当我们访问了Ctx.url之后,就会走里面的逻辑,此时this指向的是Ctx,所以可以通过this.request.url去返回。这样就做了一层代理。
简单的koa就实现了。
可以正常获取到。
响应体的处理
响应体koa实现了ctx.body,效果相当于res.end。
ctx.body也代理到了ctx.response.body去了。如:
response对象有body,属性,然后
ctx做了代理,访问ctx.body的时候返回的事ctx.response.body。
设置ctx.body的时候是设置了ctx.response.body。所以说到底还是对ctx.response.body的处理。
代理之后,我们操作了ctx.body之后,在中间件执行完毕之后就需要对body做处理。
this,compose是用来执行中间件的,先不管,当中间件执行完毕之后,就对body做处理了,然后调用res.end。所以本质还是调用res.end,只是封装了很多东西,可以直接返回object等,res.end是不可以直接返回object的。响应体的处理就是这样。
中间件的执行
koa一大特点跟express不一样的是,中间件可以使用async await,而不是回调的形式。可以执行多个中间件,通过next调用。洋葱模型~。
如图,从第一个中间件进去,打印1.然后执行next。会立马执行第二个中间件,打印2,然后执行next执行第三个中间件,打印3。然后第三个中间件执行完毕后再往回走,依次执行2,1。可以理解成从入口进入,一定会从出口出来。
但是如果next里面有异步操作。如:
r如图,第二个中间件执行的时候,用了await,后面的代码需要等待p的状态完成,而异步操作,我们第一个中间件并不会等待他,然后又返回打印1。如,
只打印1,2,1。
所以每个中间件最好使用await去执行next,这样才能保证每个中间件的内容会完全执行。
基于这个思路完成中间件的改造:
首先将每个订阅的中间件存起来。
等待请求来的时候再处理,思路就是:
封装一个dispatch函数,然后将采用递归,从第一个中间件开始执行,将dispatch函数作为next传入,索引+1,当第一个中间件执行next的时候,就会执行dispatch,执行第二个中间件,以此类推,直到所有中间件执行完毕。并且每个中间件都必须是返回pormise的函数,这样才能使用await。
简单的几行代码,贯穿koa中间件的执行原理。源码也是类似这样实现。之不过稍微复杂了点。
将ctx传入,然后通过索引i,
相当于第一次直接调用了next()去执行第一个中间件,为什么不用await,因为直接return,会等待dispatch(0)执行完毕之后再往下走。
然后第一个进去就执行了第一个中间件,并且将索引+1赋值给dispatch,作为nextf赋值给下一个中间件。以此类推,到最后中间件调用next的时候,i已经等于length了,就直接返回成功,然后再往回走。相当于洋葱模型,这样就完成了对koa中间件的处理,
错误处理
koa内部还继承了events模块,可以更加优雅的处理报错,比如执行中间件promise的时候抛错或者报错,都会走catch,然后通过events.emit(‘error’)将错误传递出去,
在外部就可以通过app.on('error‘)事件统一处理错误。
最后写一个解析请求体的中间件。
中间件一般都是返回一个函数,采用柯里化。比如解析请求体,解析请求然后将数据塞入到ctx.request.body里面。
如图,每次请求都会走这个中间件,然后将请求体解析之后塞入到了ctx.request.body里面,然后继续next往下走。
中间件的本质是用来加强koa的功能。
这样简单的koa功能就实现完毕了。
总结:
koa的优点是:中间件(洋葱模型),错误处理机制采用events,在原生的req, res之上封装了更多东西。
- koa的实现其实就是封装了http模块,实现了一个全新的ctx上下文,该上下文中有原生的req,res,也有koa自己封装的request, response
- 为了让request有更丰富的功能,将req挂载到了request上面,并且做了一层代理,采用Object.defineProperty的加简写,对requesst做了一层代理,比如访问ctx.request.url,实际上就是访问ctx.request.req.url,访问原生的url,不仅如此,在原生有的属性上,request借助一些比如url模块,采用url.parse去解析,使request的功能更加丰富。
- 然后也是采用同样的代理,将Ctx代理到ctx.request上,采用的是Object.prototype. _ _DefineGetter__的方法,因为Object.defineProperty的set方法需要借助get方法。通过代理,比如访问ctx.xx就会代理到ctx.request.xx上面去,将ctx.request.xx返回。
- 响应体的处理跟request一样的,也是通过代理,ctx.body代理到了ctx.request.body上,设置和获取其实都是处理ctx.response.body。然后在所有中间件执行完毕之后对ctx.body做处理,比如解析他的类型,设置对应请求头,然后通过res.end去返回,本质也是使用htt模块的res.end。
- koa重要的是一个中间件的处理,koa支持了async + await的中间件,像洋葱一样,可以等待中间件执行完毕之后往下走,而express不可以。
- 实现思路就是将所有中间件整合成一个promise,然后从第一个中间件开始执行,索引+1,调用next的时候又会执行第二个中间件,索引+1,直到最后调用完毕,而且每个中间件都会被包装成promise,.resolve去执行,这样就能搭配async 和await,等到所有中间件执行完毕又会一层一层往回执行,就相当于洋葱模型一样。
- 此外,koa还继承了Events模块,在通过promise捕获到错误的时候可以使用事件触发的形式传递错误,使我们编写代码更加优雅。
- 这就是koa的实现原理,封装,代理等等。
以上是关于深入node.js koa原理,实现koa的主要内容,如果未能解决你的问题,请参考以下文章
《Koa.js 设计模式-学习笔记》Koa.js第二本开源电子书完结