everlose

express

先来一段 express 代码

// app.js

var express = require(\'express\');
var path = require(\'path\');

var app = express();

// view engine setup
app.set(\'views\', path.join(__dirname, \'views\'));
app.set(\'view engine\', \'pug\');
app.use(express.static(path.join(__dirname, \'public\')));

app.use(\'/test\', function(req, res, next) {
  console.log(req.headers);
  console.log(req.body);
  console.log(req.query);
  res.header({
    \'Content-type\': \'application/json\'
  });
  res.json({
    success: true
  });
});

module.exports = app;

其 app.use 就只是把回调函数放进栈里,用 Layer 包裹,Layer 结构

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  debug(\'new %o\', path)
  var opts = options || {};
  // 处理的回调函数放这里
  this.handle = fn;
  this.name = fn.name || \'<anonymous>\';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === \'*\'
  this.regexp.fast_slash = path === \'/\' && opts.end === false
}

调用时执行中间件的 handle,中间件里再手动执行 next 来链式执行下去,删减边界判断的逻辑,大体如下

// handle 函数里
proto.handle = function handle(req, res, out) {
  var idx = 0
  var stack = self.stack;
  
  next();

  function next(err) {
    var layerError = err === \'route\'
      ? null
      : err;

    // no more matching layers
    // 函数出口。最后一个调用next,就从这里执行done出去
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }

  
    // find next matching layer
    var layer = stack[idx++];
    var match;
    var route = layer.route;
  
    var fn = layer.handle;
    fn(req, res, next);
    
  }
}

总结一下,app.use 就是往中间件数组中塞入新的中间件。中间件的执行则依靠私有方法 app.handle进行处理,按顺序寻找中间件,不断的调用 next 往下执行下一个中间件。

koa

koa 是由 compose 函数执行中间件,其实现抽离成了一个单独的包,代码简单。

function compose (middleware) {
  // ...
  
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    
    // 递归执行所有的 middleware,返回 Promise 对象。
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error(\'next() called multiple times\'))
      index = i
      let fn = middleware[i]
      // 如果中间件 next 前的代码已经执行完了
      // 接下来 fn 就被赋值为 next 后面的代码。
      if (i === middleware.length) fn = next
      
      if (!fn) return Promise.resolve()
      try {
        // 执行下一个中间件 next 前的代码。
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

但比较上文 express 最大的不同并非 async/await 实现了更强大的功能。那就是 next 后还可以执行代码

const Koa = require(\'koa\');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log(\'test1\')
  next()
  console.log(\'test3\')
});

app.use(async (ctx, next) => {
  console.log(\'test2\')
  next()
  console.log(\'test4\')
});

// response
app.use(async ctx => {
  ctx.body = \'Hello World\';
});

// 访问localhost:3000
// 打印 test1 -> test2 -> test3 -> test4
app.listen(3000);

这就是所谓的 Koa 洋葱圈模型。

分类:

技术点:

相关文章: