FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

express中间件原理connect #236

Open FrankKai opened 3 years ago

FrankKai commented 3 years ago

express中间件原理connect

不知道用了express.js的你有没有这样的疑问:

我简单看了一下connect源码,弄清楚了上面的这3个问题。

FrankKai commented 3 years ago

app.use为什么可以添加一个又一个中间件?

app.use(function middleware1(req, res, next) {
  // middleware 1
  next();
});
app.use(function middleware2(req, res, next) {
  // middleware 2
  next();
});

connect维护了一个中间件栈(middleware stack)

数据结构:栈(stack)

每次调用use,都会向这个应用(app)实例的栈(stack)推入一个带路径和处理函数的对象。

源码:

function createServer() {
  function app(req, res, next){ app.handle(req, res, next); }
  // ...
  app.stack = []; // 注意这里
  return app;
}
proto.use = function use(route, fn) {
  var handle = fn;
  var path = route;
  // ...
  // add the middleware
  this.stack.push({ route: path, handle: handle });

  return this;
};
FrankKai commented 3 years ago

connect是如何区分普通中间件和错误中间件的?

// regular middleware
app.use(function (req, res, next) {
  next(new Error('boom!'));
});

// error middleware
app.use(function onerror(err, req, res, next) {
  // an error occurred!
});

JavaScript的函数的长度属性:length。

这么说可能比较绕,看下面这个例子就懂了。

例如

function test1(foo,bar){ }
test.length // 2

function test2(foo,bar,baz){ }
test.length // 3

connect正是通过中间件处理函数的形参长度来区分出普通中间件和错误中间件的。

function call(handle, route, err, req, res, next) {
  var arity = handle.length;
  var error = err;
  var hasError = Boolean(err);

  try {
    if (hasError && arity === 4) {
      // error-handling middleware
      handle(err, req, res, next);
      return;
    } else if (!hasError && arity < 4) {
      // request-handling middleware
      handle(req, res, next);
      return;
    }
  } catch (e) {
    // replace the error
    error = e;
  }

  // continue
  next(error);
}

看了源码,官方文档对错误处理中间件描述skipping any error middleware above that middleware and any non-error middleware below的解释其实也懂了:

只能有一个异常处理中间件吗? 可以有多个。(官方文档+亲测)

app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)
// error middleware one
app.use(function onerror(err, req, res, next) {
  // an error occurred!
  next(err) // 注意要这么写,next()进入不到下一个异常处理中间件
});
// error middleware two
app.use(function onerror(err, req, res, next) {
  // an error occurred!
});
FrankKai commented 3 years ago

中间件处理函数中的next指代的又是什么?

指代的是栈中的下一个中间件。

proto.handle = function handle(req, res, out) {
  var index = 0;
  var stack = this.stack;
  // ...
  function next(err) {
    // next callback
    var layer = stack[index++];

    // call the layer handle
    call(layer.handle, route, err, req, res, next);
  }

  next();
};

通过上面代码可以看出,每次调用next()函数,会执行index++,layer为middleware stack中的下一个中间件。

其中layer.handle来自于this.stack.push({ route: path, handle: handle });