koa中间件核心(koa-compose)源码解读分析


Posted in Javascript onJune 15, 2020

最近经常使用koa进行服务端开发,迷恋上了koa的洋葱模型,觉得这玩意太好用了。而且koa是以精简为主,没有很多集成东西,所有的东西都需按需加载,这个更是太合我胃口了哈哈哈哈。

相对与express的中间件,express的中间件使用的是串联,就像冰糖葫芦一样一个接着一个,而koa使用的V型结构(洋葱模型),这将给我们的中间件提供更加灵活的处理方式。

基于对洋葱模型的热衷,所以对koa的洋葱模型进行一探究竟,不管是koa1还是koa2的中间件都是基于koa-compose进行编写的,这种V型结构的实现就来源于koa-compose。
附上源码先:

function compose (middleware) {
 // 参数middleware 是一个中间件数组,存放我们用app.use()一个个串联起来的中间件
 // 判断中间件列表是否为数组,如果不为数组,则抛出类型错误
 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
 // 判断中间件是否为函数,如果不为函数,则抛出类型错误
 for (const fn of middleware) {
  if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
 }

 /**
 1. @param {Object} context
 2. @return {Promise}
 3. @api public
  */
 
 return function (context, next) {
  // 这里next指的是洋葱模型的中心函数
  // context是一个配置对象,保存着一些配置,当然也可以利用context将一些参数往下一个中间传递
   
  // last called middleware #
  let index = -1 // index是记录执行的中间件的索引
  return dispatch(0) // 执行第一个中间件 然后通过第一个中间件递归调用下一个中间件
  
  function dispatch (i) {
   // 这里是保证同个中间件中一个next()不被调用多次调用 
   // 当next()函数被调用两次的时候,i会小于index,然后抛出错误
   if (i <= index) return Promise.reject(new Error('next() called multiple times'))
   index = i
   let fn = middleware[i] // 取出要执行的中间件
   if (i === middleware.length) fn = next // 如果i 等于 中间件的长度,即到了洋葱模型的中心(最后一个中间件)
   if (!fn) return Promise.resolve() // 如果中间件为空,即直接resolve
   try {
    // 递归执行下一个中间件 (下面会重点分析这个)
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
   } catch (err) {
    return Promise.reject(err)
   }
  }
 }
}

看到这里,如果下面的那些能够理解,那么下面的可以不用看的,还是不能理解的就继续往下看,详细一点的分析。

首先,我们用app.use()添加一个中间件,在koa的源码里app.use()这个方法就是将一个中间件push进middleware这个中间件列表里。源码里是这么写的(这个比较简单 不做分析):

use(fn) {
  if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
  if (isGeneratorFunction(fn)) {
   deprecate('Support for generators will be removed in v3. ' +
        'See the documentation for examples of how to convert old middleware ' +
        'https://github.com/koajs/koa/blob/master/docs/migration.md');
   fn = convert(fn);
  }
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
 }

compose这个方法传入一个中间件列表middleware,这个列表就是我们使用use()添加进去的方法列表,首先会判断列表是否为数组,中间件是否为方法,如果不是就直接抛出类型错误。

  1. compose返回的是一个函数,这里使用闭包来缓存中间件列表,然后这个函数接收两个参数,第一个参数是context,是一个存放配置信息的对象。第二份参数是一个next方法,也是洋葱模型的中心或者说是V型模型的拐点。
  2. 创建一个index变量来保存执行的中间件索引,然后从第一个中间件开始开始递归执行。
let index = -1
return dispatch(0)

dispatch方法就是执行中间件,先判断索引,如果i小于index那么说明在同一个中间件里执行了两次或两次以上的next函数,如果i>index则说明该中间件还未执行,那么将该中间件的所以记录下来

if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i

取出该中间件,如果i等于中间件的长图,则说明执行到了洋葱模型的中心,则最后一个中间件,如果中间件为空,那么就直接resovle掉

let fn = middleware[i]
    if(i === middleware.length){
     fn = next
    }
    if(!fn){
      return Promise.resolve()
    }

到了最刺激的部分了,也是有点绕的部分了,首先为啥return的是一个Promise的对象(Promise.resolve也是一个promise对象)呢,因为我们await next()的时候,await是等待且执行一个async函数的完成,async会默认返回一个promise对象,所以这里return的是一个promise对象。我们在每个中间里面await mext() next()指的就是下一个中间件,也就是

fn(context, function next () {
      return dispatch(i + 1)
     })

所以我们上一个中的await 等待的就是dispatch(i+1)的执行完成,dispatch返回的是Promise.resolve(fn(context, function next () { xxxx })),这样来看虽然一开始只执行了dispatch(0),但是是由这个函数形成了一条执行链。

以三个中间件执行为例,dispatch(0)执行后就形成:

Promise.resolve( // 第一个中间件
 function(context,next){ // 这里的next第二个中间件也就是dispatch(1)
   // await next上的代码 (中间件1)
  await Promise.resolve( // 第二个中间件
   function(context,next){ // 这里的next第二个中间件也就是dispatch(2)
     // await next上的代码 (中间件2)
    await Promise.resolve( // 第三个中间件
     function(context,next){ // 这里的next第二个中间件也就是dispatch(3)
       // await next上的代码 (中间件3)
      await Promise.resolve()
      // await next下的代码 (中间件3)
     }
    )
     // await next下的代码 (中间件2)
   }
  )
   // await next下的代码 (中间件2)
 }
)

先执行await上面的代码,然后等待最后一个中间件resolve一个个往上传递,这就形成了一个洋葱模型。
最后附上测试代码:

async function test1(ctx, next) {
  console.log('中间件1上');
  await next();
  console.log('中间件1下');
 };
 
 async function test2(ctx, next) {
  console.log('中间件2上');
  await next();
  console.log('中间件2下');
 };
 
 async function test3(ctx, next) {
  console.log('中间件3上');
  await next();
  console.log('中间件3下');
 };
 let middleware = [test1, test2, test3];
 
 let cp = compose(middleware);
 
 cp('ctx', function() {
  console.log('中心');
 });

OK,到这里koa2的中间件核心(koa-compose)就解析完成了,一开始看的时候,也被绕了好久,多看几遍多分析一步一步捋顺。koa1的中间件等过几天有时间再补上吧,koa1是基于generator,源码比起koa2相对简单。

最近在看koa2源码,等有时间再继续更新koa一些源码的分析。

到此这篇关于koa中间件核心(koa-compose)源码解读分析的文章就介绍到这了,更多相关koa中间件核心内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
jQuery实现手机号码输入提示功能实例
Apr 30 Javascript
js判断手机端(Android手机还是iPhone手机)
Jul 22 Javascript
基于jquery实现导航菜单高亮显示(两种方法)
Aug 23 Javascript
深入解析Backbone.js框架的依赖库Underscore.js的作用
May 07 Javascript
Bootstrap开发实战之响应式轮播图
Jun 02 Javascript
node.js express安装及示例网站搭建方法(分享)
Aug 22 Javascript
bootstrap table配置参数例子
Jan 05 Javascript
React Native中的RefreshContorl下拉刷新使用
Oct 09 Javascript
Vue入门之数据绑定(小结)
Jan 08 Javascript
Js通过AES加密后PHP用Openssl解密的方法
Jul 12 Javascript
vue基于v-charts封装双向条形图的实现代码
Dec 09 Javascript
vue实现简易计算器功能
Jan 20 Vue.js
为react组件库添加typescript类型提示的方法
Jun 15 #Javascript
JavaScript中的全局属性与方法深入解析
Jun 14 #Javascript
Vue使用Three.js加载glTF模型的方法详解
Jun 14 #Javascript
浅谈Vue 自动化部署打包上线
Jun 14 #Javascript
JS定时器如何实现提交成功提示功能
Jun 12 #Javascript
Jquery ajax书写方法代码实例解析
Jun 12 #jQuery
ng-alain的sf如何自定义部件的流程
Jun 12 #Javascript
You might like
php面向对象全攻略 (七) 继承性
2009/09/30 PHP
PHP获取文本框、密码域、按钮的值实例代码
2017/04/19 PHP
php表单习惯用的正则表达式
2017/10/11 PHP
jquery 必填项判断表单是否为空的方法
2008/09/14 Javascript
Javascript 入门基础学习
2010/03/10 Javascript
JavaScript中常见陷阱小结
2010/04/27 Javascript
jQuery中 noConflict() 方法使用
2013/04/25 Javascript
使用jquery实现放大镜效果
2014/09/02 Javascript
JavaScript生成二维码图片小结
2015/12/27 Javascript
JS简单编号生成器实现方法(附demo源码下载)
2016/04/05 Javascript
谈谈JavaScript的New关键字
2016/08/26 Javascript
jQuery中Nicescroll滚动条插件的用法
2016/11/10 Javascript
浅谈js在html中的加载执行顺序,多个jquery ready执行顺序
2016/11/26 Javascript
webpack实现热加载自动刷新的方法
2017/07/30 Javascript
jquery-file-upload 文件上传带进度条效果
2017/11/21 jQuery
基于vue实现图片验证码倒计时60s功能
2019/12/10 Javascript
微信小程序自定义navigationBar顶部导航栏适配所有机型(附完整案例)
2020/04/26 Javascript
js数组中去除重复值的几种方法
2020/08/03 Javascript
在python中的socket模块使用代理实例
2014/05/29 Python
Python编程把二叉树打印成多行代码
2018/01/04 Python
Python简易计算器制作方法代码详解
2019/10/31 Python
VSCode中autopep8无法运行问题解决方案(提示Error: Command failed,usage)
2021/03/02 Python
Web前端绘制0.5像素的几种方法
2017/08/11 HTML / CSS
施华洛世奇澳大利亚官网:SWAROVSKI澳大利亚
2017/01/06 全球购物
澳大利亚制造的羊皮靴:Original UGG Boots
2017/11/13 全球购物
比驿:全球酒店比价网
2018/06/20 全球购物
自荐书模板
2013/12/19 职场文书
关于安全演讲稿
2014/05/09 职场文书
环保志愿者活动方案
2014/08/14 职场文书
保研推荐信格式
2015/03/25 职场文书
教师培训学习心得体会
2016/01/21 职场文书
母婴行业实体、电商模式全面解析
2019/08/01 职场文书
Python基础教程,Python入门教程(超详细)
2021/06/24 Python
Java数组与堆栈相关知识总结
2021/06/29 Java/Android
Python集合的基础操作
2021/11/01 Python
Windows server 2012 配置Telnet以及用法详解
2022/04/28 Servers