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 相关文章推荐
可以将word转成html的js代码
Apr 11 Javascript
基于jquery实现的一个选择中国大学的弹框 (数据、步骤、代码)
Jul 26 Javascript
ExtJS DOM元素操作经验分享
Aug 28 Javascript
js完美解决IE6不支持position:fixed的bug
Apr 24 Javascript
JavaScript中使用concat()方法拼接字符串的教程
Jun 06 Javascript
JavaScript处理解析JSON数据过程详解
Sep 11 Javascript
简单谈谈Javascript中类型的判断
Oct 19 Javascript
Bootstrap CSS布局之列表
Dec 15 Javascript
js时间控件只显示年月
Jan 08 Javascript
js仿iphone秒表功能 计算平均数
Jan 11 Javascript
js脚本编写简单刷票投票系统
Jun 27 Javascript
vue实现点击图片放大效果
Aug 15 Javascript
为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下批量挂马和批量清马代码
2011/02/27 PHP
探讨PHP调用时间格式的参数详解
2013/06/06 PHP
php preg_replace替换实例讲解
2013/11/04 PHP
PHP中变量引用与变量销毁机制分析
2014/11/15 PHP
Zend Framework实现多服务器共享SESSION数据的方法
2016/03/22 PHP
PDO::getAvailableDrivers讲解
2019/01/28 PHP
php的命名空间与自动加载实现方法
2019/08/25 PHP
打开超链需要“确认”对话框的方法
2007/03/08 Javascript
javascript 按回车键相应按钮提交事件
2009/11/02 Javascript
js函数的延迟加载实现代码
2012/10/11 Javascript
JavaScript中的原始值和复杂值
2016/01/07 Javascript
AngularJS 基础ng-class-even指令用法
2016/08/01 Javascript
jQuery 检查某个元素在页面上是否存在实例代码
2016/10/27 Javascript
Bootstrap 3.x打印预览背景色与文字显示异常的解决
2016/11/06 Javascript
js拖拽功能实现代码解析
2016/11/28 Javascript
js Canvas绘制圆形时钟教程
2017/02/06 Javascript
JavaScript实现的简单Tab点击切换功能示例
2018/07/06 Javascript
如何理解Vue的v-model指令的使用方法
2018/07/19 Javascript
微信小程序列表中item左滑删除功能
2018/11/07 Javascript
JavaScript数组类型Array相关的属性与方法详解
2020/09/08 Javascript
[45:38]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#1Liquid VS Alliance第一局
2016/03/02 DOTA
[09:40]DAC2018 4.5 SOLO赛 MidOne vs Miracle
2018/04/06 DOTA
使用Python脚本来控制Windows Azure的简单教程
2015/04/16 Python
Python多线程爬虫简单示例
2016/03/04 Python
PyQt5实现五子棋游戏(人机对弈)
2020/03/24 Python
python模块常用用法实例详解
2019/10/17 Python
Python 调用 ES、Solr、Phoenix的示例代码
2020/11/23 Python
玩具反斗城葡萄牙官方商城:Toys"R"Us葡萄牙
2016/10/21 全球购物
英国二手iPhone、音乐、电影和游戏商店:musicMagpie
2018/10/26 全球购物
英国马匹装备和马术用品购物网站:Equine Superstore
2019/03/03 全球购物
资金主管岗位职责范本
2014/03/04 职场文书
参赛口号
2014/06/16 职场文书
党的群众路线教育实践活动党员个人剖析材料
2014/10/08 职场文书
教您:房贷工资收入证明应该怎么写?
2019/08/19 职场文书
成本低的5个创业项目:投资小、赚钱快
2019/08/20 职场文书
导游词之峨眉乐山/兵马俑/北京故宫御花园
2019/09/03 职场文书