Koa洋葱模型的另种实现

  • 2019-11-23
  • 0
  • 0

洋葱模型如下图:
onion model

middleware1而言,其next就是middleware2,同理middleware2next就是middleware3

用函数表示为:middleware1(middleware2(middleware3())),类似函数式编程中的compose,同步情况下的简单写法就是这样:

function compose(middlewares){
    // 期望返回一个层层包裹的middleware函数
    // 这个函数接收一个next函数
    return middlewares.reduce((last, cur) => next => last(() => cur(next)));
}

// 这样就完成了一个同步的模型,测试一下

compose([
  function(next) {
    console.log("before a");
    next();
    console.log("after a");
  },
  function(next) {
    console.log("before b");
    next();
    console.log("after b");
  }
])(() => {});
/**
before a
before b
after b
after a
*/

基于上面这个基本版,稍加改造处理一些边界情况即可:

function compose(middlewares) {
  if (!Array.isArray(middlewares)) throw new TypeError('Middlewares must be an array!');
  for(let item of middlewares){
    if (typeof item !== 'function') throw new TypeError('Middleware must be componsed of function');
  }
 const noop = () => {};
 // reduce 0个或1个中间件时的占位
const emptyMiddleware = async (_, next = noop) => next();

 return middlewares.reduce(
    (last, cur) => (ctx, next) => {
      // 防止next被多次执行的守卫
      const guard = (fn = noop) => {
        let runed = false;
        // next 需要返回Promise
        return async () => {
          if (runed) throw new Error('next() should not be called multiple times in one middleware!');
          runed = true;
          return fn(ctx, () => {});
        };
      };
      return last(
        ctx,
        guard(() => cur(ctx, guard(next)))
      );
    },
    emptyMiddleware
  );
}

使用:

compose([
    async (ctx,next)=>{
        console.log('before a')
        await next()
        console.log('after a')
    },
    async (ctx,next)=>{
        console.log('before b')
        await next()
        console.log('after b')
    }
])()

/**
before a
before b
after b
after a
*/

已跑通全部用例: https://github.com/koajs/compose/blob/master/test/test.js

上一篇:    下一篇:

评论

还没有任何评论,你来说两句吧

发表评论

me@ccc5.cc - 衫小寨