RSS

koa中间件框架的的原理

express是一个相对完整的node服务端框架,自身集成了router。koa是一个相对纯粹的中间件web框架,基本只负责处理request和response、添加中间件和启动服务。其他的web相关功能都是通过中间件插拔式的集成安装进去的,所以koa的代码也非常简单,但是非常经典,体现了JavaScript函数式编程的精髓。koa还支持了Promise,Generator Function,让我们使用koa写异步编程代码十分的轻松。

一、koa中间件的添加

koa的中间件就是一个JavaScript的Function对象,参数是context和next,其中context是koa框架提供的一个对象,包含koa的request对象和response对象。next是一个koa提供给开发者的Function,用以执行下一个中间件。

  1. 中间件的添加,直接添加到application的middlewares数组中。

     use(fn) {
         if (isGeneratorFunction(fn)) {
           fn = convert(fn);
         }
         this.middleware.push(fn);
         return this;
       }
    

    如果中间件添加的是一个Generator Function,则将该function转换成一个普通的function。

     use(fn) {
         if (isGeneratorFunction(fn)) {
           fn = convert(fn);
         }
         this.middleware.push(fn);
         return this;
       }
    

    那么什么是Generator Function,中文翻译为迭代器对象生成函数,那什么又是迭代器对象呢?

  2. 迭代器对象

    迭代器对象就是一个普通的Object类型的对象,提供一个next方法访问起内部迭代数据,返回 数据以及是否迭代完成。

     // 一个简单的迭代器对象
     var it = {
       var i = 0;
       next: function() {
         if (i < 10) {
           return { done: false, value: i };
         } else {
           return { done: true, value: -1 };
         }
         i ++;
       }
     }
    
     // 我们可以这么使用该迭代器对象
     let result = it.next();
     while (!result.done) {
      console.log(result.value); 
      result = it.next();
     }
    

    如果我们自己编写迭代器对象,需要小心维护其内部状态,而且也比较麻烦,利用迭代器对象生成器我们可以很方便的创建迭代器对象。

  3. 迭代器对象生成器
     function * makeIterator() {
       var i = 0;
       while(i < 10) {
         yield i++;
       }
     }
    
     var it = makeIterator();
    

    显然通过这种方式构造迭代器对象要简洁得多。

    我们知道koa中间件我们可以定义成一个迭代器对象生成器,内部用yield Promise的方式控制异步调用,如果我们自己调用迭代器对象生成函数生成的迭代器,那么我们就可以遍历里面的异步Promise,然后用then方法将它们连接起来按照顺序执行,但是这些事情现在都不需要我们自己做,伟大的co模块帮我们做了这件事情,这也是koa将一个迭代器生成函数convert成一个普通函数的方法。

  4. 将koa中的Generator中间件转换成一个普通的中间件

     function convert (mw) {
       const converted = function (ctx, next) {
         return co.call(
           ctx,
           mw.call(
             ctx,
             (function * (next) { return yield next() })(next)
           ))
       }
       return converted
     }
    

    有一说一,这段代码我似懂非懂。

转换之后,添加到middlewares数组中的所有中间件就是形如 function (ctx, next){}

二、koa中间件的执行

实际上,koa中间件执行最重要的环节就是将所有的中间件compose成一个。

function compose (middleware) {
  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)
      }
    }
  }
}