最近公司的技术分享涉及Koa,于是重新刷了一遍,学习过程中主要思考了以下几个问题:
- koa的应用场景
- koa的优势
- koa内部是如何实现的?
一、Koa的应用场景
任何东西的出现都有其合理性, Koa是为了更方便构建http服务而诞生的一个NodeJs框架。没错,只会Js的你也可以curl了。
二、koa的优势
Koa的优势总结下来有两点:
- 1.更轻量化,小而美。
- 2.洋葱模型,更好地控制中间件服务的异步逻辑。
在Koa出现之前其实还有Express框架,Koa也是Express原班人马打造的。Express可以理解为http服务的大杂烩,各种功能应有尽有,并且都放在一起实现。既然有前者的经验铺路,Koa的实现更加精简,只负责核心逻辑,想要什么功能,则通过中间件的形式引入就行,好比如打火锅,想吃什么就把什么加进入。
在学习的过程中,也参考了很多学习资料,很多都提起看了koa的洋葱模型,通篇下来解释了一遍,我还是有不少疑惑。
我一直在想,洋葱模型,究竟适合什么业务场景?
asyn await语法的出现在一定程度来说,有推进Koa2的发展,使得异步处理逻辑更加清晰。举个🌰
1 |
|
因为存在一些语法糖,通过promise还原上述代码:
1 | function log() { |
也就是说,调用next会给我们返回一个promise对象,而promise何时会resolve就是koa内部源码做的处理。
再详细分析下koa的洋葱模型上诉处理流程:
- 获取当前时间戳requestTime
- 调用next()执行后续的中间件,并监听回调
- 第二个中间件可能会调用第三个、第四个、第五个, 但这不是log所关心的,log只关心第二个中间件什么适合resolve,而第二个中间件的resolve则依赖他后边的中间件resolve
- 第二个中间件resolve,意味着后面的中间件执行,并且全部resolve了,此时才会执行log中next后面的代码。
本文只是以log为例子,简单介绍了一下koa中间件的一些核心概念,在真正的业务场景当然不会这么简单。
koa中间件存在的真正意义是什么?我又陷入了思考,谈下我个人的理解。客户端发送一次http请求,服务端在响应数据给客户端之前,可以通过中间件的组合,进行拦截处理。比如,整合请求参数、对请求的url进行差异化处理,查询数据等等,因为有asyn await的支持,中间件的编写从代码的优雅性来说,避免了then,callback的写法,总体来说还是挺舒服的~
三、koa内部是如何实现的?
打开koa的源码,发现核心code都放到了lib下,分别有如下几个文件:
- application.js
- contenxt.js
- request.js
- response.js
koa把NodeJs原生请求与响应重新封装了一遍,通过get, set的方式获取数据以及修改数据,并整合到context中。
1 | import koa from 'koa' |
通过几行简单的代码就可以开启一个http服务,试想下如果通过原生的createHttpServe, 得花多大的劲,源码不难理解,如果你去刷一遍源码,就可以感觉到前面为什么说koa小而美了。
阅读源码过后,有以下三点我觉得是比较有意思的:
- 客户端请求一次,koa内部都会通过const ctx = this.createContext(req, res)重新创建一个context对象, 源码如下:
1 | callback() { |
- koa实际上是继承NodeJs的Emitter事件的,为什么需要这样做? 很重要的一个原因是koa提供订阅/发布功能,如果程序发生异常,就可以调用ctx.onerror(err),监听事件集中处理异常情况,源码如下:
1 | handleRequest(ctx, fnMiddleware) { |
- 中间件的处理逻辑
如下,通过use方法,函数作为use方法的参数(函数的参数固定,第一个参数为context,第二个参数为next,表示下一个中间件函数),创建了三个中间件,内部的处理依次调用this.middleware.push(fn),将use的参数塞进数组中。
1 | app.use((ctx, next) => { |
koa洋葱模型的核心代码就是koa-compose库,代码只有短短的几十行,牛逼~
客户端进行请求,触发compose(this.middleware), 从源码我们发现,此时自动执行第一个中间件,从而顺序遍历middleware所有元素,递归执行dispatch,此时洋葱由外到内,直至所有中间件都执行完成后,resolve, 洋葱从内到外,源码如下:
1 | function compose (middleware) { |
上面我们知道,中间件的注册是通过如下的形式
1 | app.use((ctx, next) => { |
敲黑板,留意这行代码Promise.resolve(fn(context, dispatch.bind(null, i + 1))), 其中dispatch.bind(null, i + 1))就是next(), koa内部会将我们手动执行下一个中间件。
感悟
或许有一天专门跑去写NodeJs了呢? 哈哈