Koa洋葱模型的另种实现

洋葱模型如下图:
onion model

middleware1而言,其next就是一个函数,返回middleware2的执行结果(一个promise对象),同理middleware2next就是一个函数,其返回middleware3的执行结果(又是一个pormise对象)…以此类推。

用函数表示为: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!');

  middlewares.forEach(item => {
    if (typeof item !== 'function') throw new TypeError('Middleware must be componsed of function');
  });

  const noop = () => {};
  // next=noop work for 0 middleware
  const emptyMiddleware = async (_, next = noop) => next();

  // 保证在一个中间件中next只被调用一次
  const guard = ctx => next => {
    let runed = false;
    // next 返回promise,包装一层依然要返回promise
    return async () => {
      if (runed) throw new Error('next() should not be called multiple times in one middleware!');
      runed = true;
      // 这里保证 middlewareChain(ctx,middleware) 正常
      return next(ctx, noop);
    };
  };

  return middlewares.reduce(
    // middlewareChain可能没被传入next
    (last, cur) => (ctx, next = noop) =>
      // 不看guard的话很好理解  last(ctx,() => cur(ctx,next))
      // 白话就是 前一个middleware 的参数为一个函数 返回当前middleware的执行结果
      // 而当前middleware的执行需要传入next
      last(
        ctx,
        guard(ctx)(() => cur(ctx, guard(ctx)(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