koa 核心拓展——中间件机制

Posted 阿阿阿阿阿阿杰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了koa 核心拓展——中间件机制相关的知识,希望对你有一定的参考价值。

写这篇文章的初衷来源于跟一个搞 java 的老弟讨论静态服务的话题。
老弟:node 是如何实现一个静态服务的。
我:node 有几个常用的静态服务中间件,目前读过 koa-static 的源码(其他中间件的做法应该也差不多)。
然后我巴拉巴拉…加上给他截图的源码。
重点来了…
老弟: await next() 这行代码什么意思,next 对象是什么,他怎么知道 next 下一个要执行的是什么?  
一连串的问题,既然要刨根问底,那么我也只能整理思路从源码出发进行讲解,随后也就有了本篇文章,记录一下。

源码分析

废话环节结束,进入正题,直接上源码:
可以看到这段代码非常简短,也比较简单。这段源码是 koa-compose 这个中间件,处理所有通过 use 注册的中间件,接下来分析他做了什么。
图上可以看出他 return 出的是一个函数,也是核心代码,按照逐行。
  • 第一行,这句话的意思是检查参数是否是数组,如果不是,抛错(保证传进来的必须是一个中间件数组)。

  • 2,3,4 遍历检查该参数的元素是否是 function,若有,抛错。

剩下的是关键代码:
function (context, next) context 就是我们日常用的 context 对象,在 http 监听到相应路由的时候会传入(koa 里面有,这里不介绍了), next 等下解释,往下看。
这个函数 return 的是 dispatch 函数执行返回的对象, dispatch 后面会发现是一个递归函数,根据 i 动态执行,return 的值有三种情况:
  • Promise.reject(err)

  • Promise.resolve()

  • Promise.resolve(fn())

dispatch

第一行: i <= index, 符合条件,抛错。这里的错误有两种情况:
1.i 为 初始值 -1 时,小于或等于 -1 都属于下标越界。 
2.当前中间件和上次或上次以前的中间件是同一值,表示已经调用过了(这里是组装,最后面会讲到),通常出现这种情况是因为注册了多次或传进来的中间件数组有重复。
第二行:index = i,更改 index,备下一次迭代做检查,是为上一行服务的。
第三行:取出索引为 i 的中间件,下面的 fn 均是。
第四行:检查是否是最后一个中间件的下一个元素,是,把 fn 替换 为 next。
第五行:实际上这行是检查第四行,如果没有传 next,说明没有要初始化的中间件了,返回 Promise.resolve()。
第六行:跳过。

第七行:精华在这里!品,你细品…

  • context,把 context 传递到 fn 中。

  • dispatch.bind:bind,返回的是一个懒加载函数,第一个是引用指向,第二个就是把 下一个索引传进去,作用实际上就是把下一个中间件传递。

ok,完整代码就分析完了,然后全部串起来,仔细想,这块代码最后干了啥!!!
如果一次一次执行,就会发现 最初的是 [fn1,fn2,fn3,fn4,fn5],最后变为…这里还是写伪代码比较直观
  
    
    
  
function finalFn(ctx{
   return  Promise.resolve(f1(ctx, 
     Promise.resolve(f2(ctx, 
         Promise.resolve(f3(ctx, 
             Promise.resolve(f4(ctx, 
                 Promise.resolve(f5(ctx,
                     Promise.resolve()
                    ))
                ))
            ))
        ))
    ))
}
更简化来表示 f1(fn2(f3(f4(f5())))),到这里就很清楚了这个中间件做了什么事情了吧!
上面整理的结果,也就是大家常说的 洋葱模型 ,因为他真的很像洋葱,一层一层拨开(心里的BGM有点刹不住了…)
下面上两张图: 
觉得很有意思的同时,咱们再想一下为什么要这么做呢?其实,这个中间件处理机制跟着常用设计模式的 责任链模式极为相像,责任链模式就是采用引用传递的模式来执行下个操作,好处也是面向对象中老生常谈的 解耦,还有一个好处是 灵活性,允许动态的新增,修改或删除等操作。

示例

下面通过写一个中间件来体验一下。
  
    
    
  
const koa =  require( "koa");
const parseBody =  require( "koa-body");
const app =  new koa();
app.use(parseBody());
const mergeParams =  async (ctx, next) => {
    ctx.params = {};
     Object.assign(ctx.params, ctx.query, ctx.request.query);
     await next();
}
app.use(mergeParams);
app.use( ctx => {
     console.log(ctx.params);
    ctx.body =  "success";
});
app.listen( 3000);
上边这段代码做的事情是给ctx对象创建一个params对象,将ctx.query和ctx.request.body 的参数都放到params里。
测试:
node serve.js
在浏览器,访问 
http://127.0.0.1:3000/user?id=1

结语

到这里本篇文章就结束了,篇幅挺小的代码,实现的功能却很强大,壮哉!追其源,观其质,乐哉!

以上是关于koa 核心拓展——中间件机制的主要内容,如果未能解决你的问题,请参考以下文章

Koa 系列 — Koa 中间件机制解析

koa中间件机制详解

玩转Koa -- 核心原理分析

深入解析Koa之核心原理

全栈项目|小书架|服务器开发-Koa2中间件机制洋葱模型了解一下

KOA学习笔记