天天看点

Koa 解析Koa 介绍自我定位中间件处理逻辑错误处理逻辑更小的核心代码Koa 解析Koa 初始化应用实例app.use() 添加中间件app.listen() 监听--核心逻辑解析 createContextkoa-compose 实现洋葱圈模型解析 handleRequest总结

作者:闪念基因
Koa 解析Koa 介绍自我定位中间件处理逻辑错误处理逻辑更小的核心代码Koa 解析Koa 初始化应用实例app.use() 添加中间件app.listen() 监听--核心逻辑解析 createContextkoa-compose 实现洋葱圈模型解析 handleRequest总结

在 koa 的官网上,slogan 是: 基于 node.js 平台的下一代 web 开发框架 。我们都知道, koa 与 express 都诞生自同一个团队。如何解析这个 slogan?

该 slogan 中所有包含“比较”含义的词汇,都可以理解为是相对 express 而言的。所谓的"下一代""更小""更快",都特有所指。我们来看看现阶段 express 与 koa 的下载量对比:

Koa 解析Koa 介绍自我定位中间件处理逻辑错误处理逻辑更小的核心代码Koa 解析Koa 初始化应用实例app.use() 添加中间件app.listen() 监听--核心逻辑解析 createContextkoa-compose 实现洋葱圈模型解析 handleRequest总结

可以看出,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 。可参考下面的动图来过一遍上面代码的实现。

Koa 解析Koa 介绍自我定位中间件处理逻辑错误处理逻辑更小的核心代码Koa 解析Koa 初始化应用实例app.use() 添加中间件app.listen() 监听--核心逻辑解析 createContextkoa-compose 实现洋葱圈模型解析 handleRequest总结

我们来看看,得到了每个请求的上下文信息 context 与 经过 compose 后的中间件逻辑之后,我们来看看最终如何处理请求的:

执行 compose 后的中间件函数,最后执行 respond 方法。经查看,可以知道,主要是对响应进行封装,并且赋值给到 context.res.end(body) 。

综上,我们简单地对 koa 进行了介绍;同时通过解析 demo 文件的执行过程,我们从源码级别窥见了 koa 的一些相关原理实现。至此,我们就已经将 koa 相关的介绍与解析讲完了。我在写下来本文章的过程中,会对整个过程更加理解,也希望对你有所帮助!

继续阅读