最近经常使用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中间件核心内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

koa中间件核心(koa-compose)源码解读分析的更多相关文章

  1. 一文详解 Compose Navigation 的实现原理

    这篇文章主要介绍了一文详解 Compose Navigation的实现原理,文章通告围绕主题展开详细的相关内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

  2. Koa项目搭建过程详细记录

    本篇文章主要介绍了Koa项目搭建过程详细记录,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. 使用Compose制作抖音快手视频进度条Loading动画效果

    这篇文章主要为大家介绍了使用Compose制作抖音快手视频进度条Loading动画效果,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. 利用Jetpack Compose实现绘制五角星效果

    这篇文章主要为大家介绍了Jetpack Compose如何使用自定义操作符实现绘制五角星效果,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下

  5. 深入koa-bodyparser原理解析

    这篇文章主要介绍了深入koa-bodyparser原理解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  6. 傻瓜式解读koa中间件处理模块koa-compose的使用

    这篇文章主要介绍了傻瓜式解读koa中间件处理模块koa-compose的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. node.js 中间件express-session使用详解

    这篇文章主要给大家介绍了node.js中间件express-session使用的相关资料,文中介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。

  8. node中koa中间件机制详解

    本篇文章主要主要介绍了node中koa中间件机制详解,详细的介绍了koa和兼容问题,具有一定的参考价值,有兴趣的可以了解一下

  9. Android Compose自定义TextField实现自定义的输入框

    众所周知Compose中默认的TextField和OutlineTextField样式并不能满足所有的使用场景,所以自定义TextField就成了必备技能。本文将自定义TextField实现自定义的输入框,感兴趣的可以了解一下

  10. Android开发Compose remember原理解析

    这篇文章主要为大家介绍了Android开发Compose remember原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部