在 koa 的官网上,slogan 是: 基于 node.js 平台的下一代 web 开发框架 。我们都知道, koa 与 express 都诞生自同一个团队。如何解析这个 slogan?
该 slogan 中所有包含“比较”含义的词汇,都可以理解为是相对 express 而言的。所谓的"下一代""更小""更快",都特有所指。我们来看看现阶段 express 与 koa 的下载量对比:
可以看出,express 的市场占有量与下载量(以及增速)都远远高于 koa。所以自称为“下一代”并不为过(当然,这个数据层面来对比是否公平,值得商榷)。
同时,与 express 内部囊括了一套基础的业务处理框架的做法不同,koa 只包含了被称为“洋葱圈模型”的中间件核心处理逻辑。koa 期望由社区来健全完善更多的业务模块。这样看来,将 koa 看做 express 之前内置的 connect 来对比更合适(express 4 内部已经移除 connect)。不过,“更小”的 koa 定位更加明确,其内部一些突出的特性也让开发者感到欢喜。
接下来,我们就来详细看看 koa 相比于 express 有什么不同?只有理解了不同,我们才会更加理解 koa 的特性。
在 koa 官方网页上,有这样一段关于 koa 定位的描述:
摘抄网络上的一段翻译如下:
从理念上来说,koa 意图“修复并取代 node”,而 express 做的是“增强 node”。koa 使用 promise 和 async 函数来摆脱回调地狱并简化异常处理逻辑。它暴露了自身的 ctx.request 和 ctx.response 对象而取代了 node 的 req 和 res 对象。
express 从另一方面,通过增加额外的属性和方法增强了 node 的 req 和 res 对象,并引入了许多框架上的功能,例如路由和模板,而 koa 没有这么做。
其实现是否符合定位,各位可自行看待。
在本质上,koa 与 express 4 的区别在于下面两个点:
koa 是使用 promise + async/await 来处理中间件传递逻辑;而 express 4 是使用回调函数来处理中间件传递逻辑。
koa 是走完所有的中间件处理逻辑才最终返回请求;而 express 4 是遇到 res.send() 就返回请求。
借助于新 es 规范中 async/await 写法的 promise 支持,koa 能够完美支持异步处理方式与摆脱回调地狱。下面的模拟实现,看代码的区别就可以体会到两者的不同之处。
在 koa 内部,由于是洋葱圈模型,正常的中间件处理过程可以不处理报错逻辑,只需要在中间件数组的第一个中间件设置为错误函数中间件即可。而在 express 4 中,需要在每一个正常的中间件中都包含错误处理逻辑,并且需要通过 next() 函数抛出来,否则该错误将会被隐没。
与 express 4 包含有齐整的 web 工具(路由、模板等)不同,koa 只专注于核心代码。因此它的体积更小。
简单的描述并不能让我们对 koa 的原理有更深的理解。我们尝试来看看源码。最简单的演示代码:
在这里,我们想要去源码上弄明白几个事情:
koa 应用中,我们通过 const app = new koa() 来构建一个实例应用。核心代码(有删减):
初始化应用实例的过程可以看出:为 app 实例添加 context 、 request 、 response 、 middleware 等属性。 在这个初始化的过程中,koa 会把 koa 官方提供的方法都挂载到相应的位置,方便在代码中调用。
在这里会检查传入函数的类型,如果是老的 generator 函数类型会转换一下,然后直接放到 middleware 这个数组中。数组中的中间件,会在每一个请求中去挨个执行一遍。
在 listen 函数执行的时候,才会创建 server :
在 node 的 http 模块,对于每一个请求,都会走到回调函数 callback 中去。所以这个 callback 是用于处理实际请求的。我们来看看 callback 做了啥:
这里的内容并不简单,涉及到几个点:
在这里主要是明确 context 挂载的内容,这个 context 是 每一次请求都会生成 的。由代码可知:
每个应用实例 app 都会有对应的 context 、 request 、 response 实例(即 this.xxx )。每个请求,都会基于这些实例来创建自己的实例。这样做的目的是为了不污染全局的变量。
将 node 原生的 req 、 res 以及 this 挂载到 context 、 request 、 response 上。
将创建的 context 返回,传入所有中间件的第一个参数,作为这个请求的上下文。
着重解释一下第 2 点,为什么要把这些属性挂载上去。除了便利性外,我们通过查看源码 resquest.js response.js 文件还可以知道:所有的这些访问都是代理,最终访问的还是 node 原生的 req 、 res 。
接下来,我们来看看源代码中的 koa-compose 插件是如何实现洋葱圈模型:
简单来看,在 compose 中会根据变量 i 挨个执行中间件,以递归的方式来执行。 整个过程不难理解,实现较为巧妙。 值得注意的是:每一次 dispatch() 的返回值都是 promise 。可参考下面的动图来过一遍上面代码的实现。
我们来看看,得到了每个请求的上下文信息 context 与 经过 compose 后的中间件逻辑之后,我们来看看最终如何处理请求的:
执行 compose 后的中间件函数,最后执行 respond 方法。经查看,可以知道,主要是对响应进行封装,并且赋值给到 context.res.end(body) 。
综上,我们简单地对 koa 进行了介绍;同时通过解析 demo 文件的执行过程,我们从源码级别窥见了 koa 的一些相关原理实现。至此,我们就已经将 koa 相关的介绍与解析讲完了。我在写下来本文章的过程中,会对整个过程更加理解,也希望对你有所帮助!