【进阶全栈-02】:Koa源码分析(中间件执行机制、Koa2与Koa1比较)

1、why Koa

  1. Koa是由Express原班人马打造,可是相较于Express的大而全,Koa是小而精的。Koa没有绑定不少的框架以及插件,更容易让咱们进行扩展,包括如今较为流行的EggJS and ThinkJS都是基于Koa开发的。
  2. Koa避免了Express中间件基于callback形式的调用,它使用了咱们JS新版本特性,Koa1中间件借助于咱们的co and generator特性,Koa2借助了Promise and async await特性,更好的进行流程控制以及catch咱们的错误
  3. Koa提供了Context对象,其实是对咱们node中request and response对象的封装,咱们不须要不少的手动处理咱们的request and response对象。Context是贯穿咱们整个请求的过程,咱们能够中间件须要传递参数挂在到Context对象上。(栗子:咱们能够将用户信息挂在它上面,经过ctx.state.user进行操做。)
  4. Koa的中间件执行机制:洋葱圈模型它不是按顺序执行的,多个中间件会造成一个先进后出的栈结构,当前中间件掌握下一个中间件的执行权,对于流程控制以及后置处理逻辑的实现很是有效

2、Koa2与Koa1的比较

  1. 中间件的管理方式Koa1借助co and generator管理咱们的中间件,Koa2借助async await(async函数返回的是Promise对象)管理咱们的中间件。
  2. context对象获取Koa1经过this对象(this.req,this.res)获取,Koa2经过ctx参数(ctx.req, ctx.res)获取。
  3. 社区成熟度Koa2的轮子多且成熟,生态比Koa1丰富。

3、举个栗子,剖析源码

1. 栗子

image.png
这段代码呢,经过 listen方法建立了咱们的http服务器,端口是 3000。而且经过 use方法传入了三个 koa中间件, 而且 console出咱们的执行结果。

2. 从listen方法提及

  1. 首先咱们去github上clone最新的Koa的源代码。而后源码结构以下:

image.png

  1. 能够看得出来整个源码的核心代码皆在lib文件,咱们在栗子中require('koa')其实是引入的lib/application.js里面的Application类。下面咱们来分析一下咱们的listen方法的实现,首先咱们看一下这个Application类里面到底有些什么:

image.png

  1. 咱们能够看到Application类下有咱们的listen方法,方法以下:

image.png
能够看出这里实际上 仍是经过咱们Node.js的http模块,经过createServer建立一个http服务器, 而后listen方法接收咱们的端口,这和咱们使用原生Node.js建立一个http服务器是同样的。 这个this.callback()也就是咱们createServer的入参,这个参数是一个函数,是做为 request 事件的监听函数,这个函数还接收两个参数req、res,也就是咱们的请求对象以及响应对象。那咱们能够推断出这个 listen方法里面的 this.callback()实际上也返回是一个函数,这个函数接收req、res两个入参。

  1. 接下来咱们再看一下这个this.callback()里的callback方法是啥样的:

image.png
callback方法内的第一句fn = compose(this.middleware),这个this.middleware是咱们在Application类中的constructor就定义了,是一个数组,这里实际上存储的就是咱们举的栗子中的app.use()的入参方法,也就是咱们Koa的中间件。 这个compose方法很重要,主要是控制咱们的中间件的执行,下文讲中间件的执行机制会仔细说说这个compose。而后看这个函数返回,果真和咱们推断的同样, 这个callback方法最终返回的是一个handleRequest方法,接收req,res两个参数。handleRequest方法中,第一句this.createContext方法实际上就是对咱们req、res对象的封装
image.png
经过createContext 建立了咱们ctx对象。 这个callback方法返回的handleRequest方法第二句是返回了Application类上的handleRequest方法的执行结果(注意这里有“重名”的方法,注意区分),方法以下:
image.png

这个方法接收两个参数: 一个是咱们的compose的返回结果fn、另外一个是咱们的ctx对象,这个方法主要是对咱们请求的处理以及错误的统一捕获以及处理javascript

3. 执行一下咱们的栗子:扒一扒中间件的执行机制

Koa的中间件执行机制有个形象的称呼-洋葱圈模型。咱们先执行栗子代码,看看输出的结果究竟是什么?     前端

image.png

4. 理解use方法

image.png
app.use实际上就是调用了Applcation类下面的use方法,这个方法主要是先判断入参形式是不是一个函数等(参数校验),若是入参是一个generator函数,这也就是koa1中的写法,在koa2中须要koa-convert去转换一下。而后 将咱们入参传入的中间件函数push到this.middleware数组中

5. 中间件执行机制的核心就是compose

再回到上文提到的callback函数,咱们的中间件执行机制的核心就是compose(this.middleware),下面咱们来分析一下compose函数的源码: java

image.png

  1. 先校验咱们middleware的参数正确性,是不是数组,数组项是否为函数;
  2. compose函数其实是返回一个function,这个返回的function,在上面也说起,最终是传入到handleRequest方法中而后传入ctx参数:fnMiddleware(context)。
  3. 再回到咱们的compose中return的这个function,接收两个参数:第一个就是咱们handleRequest方法传入的ctx对象,第二个next呢,其实是传入的一个方法,这个方法是在全部middleware执行完毕后,最后执行处理的函数。这个function核心就是递归执行咱们的middleware。要理解这段代码,先要理解Promise.reslove()
// Promise.reslove返回一个fulfillled状态的promise对象
// 能够当作new Promise()的快捷方式

Promise.reslove(fn(context, dispatch.bind(null, i+1)));
// 其实是等于
new Promise((relove, reject) => {
  reslove(fn(context, dispatch.bind(null, i+1)));
})
复制代码
  1. 仔细阅读这个function, 它是先定义了一个index(利用咱们的闭包每次执行一次dispatch方法去改变index),执行了dispatch(0),这个dispatch方法就是咱们执行机制实现的核心,dispatch函数里面若是没有执行到最后一个middleware,就返回了Promise.reslove(fn(context, dispatch.bind(null, i + 1))), 这个fn(context, dispatch.bind(null, i + 1))也就是执行咱们经过app.use加入的middleware函数,middleware函数统一接收两个参数一个是context,一个是next:下一个middleware函数,这样能够看出来若是咱们koa中某个中间件没有执行next方法,那么以后加入的中间件是不会执行的。这也就造成了咱们的洋葱圈模型
// 核心方法:递归调用咱们middlewares, 基于Promise进行异步流程控制;
// Promise.resolve()返回的是一个thenable对象;
// 因此咱们koa2中中间件都基于async函数,await等待下个中间件方法的执行;
return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
复制代码

4、总结

  1. Koa这个框架是小而精美的,它的源码是也很是少,只有一千多行。中间件的执行机制主要是读懂这个koa-compose的代码,多个中间件会造成一个先进后出的栈结构,当前中间件掌握下一个中间件的执行权
  2. Koa自己的功能多是知足不了咱们平常开发的需求的,咱们能够经过许多的第三方的包,也能够自定义中间件来辅助咱们的开发。(了解了中间件机制,自定义一个中间件就很是容易了)
  3. Koa1借助co and generator管理咱们的中间件,Koa2借助async await(async函数返回的是Promise对象)管理咱们的中间件

5、Koa2实战

这是一个全栈开发实战实例:koa2-mysql-sequelize-JWT(供参考交流,一块儿学习)。node

6、前端修炼指南(但愿能够对您有帮助)

这是一个我的博客(前端修炼指南):front-end-Web-developer-interviewmysql

6、参考文档

  1. Koa2 还有多久取代 Express
  2. Koa vs Express && Koa1 vs Koa2
相关文章
相关标签/搜索