Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

express中间件原理connect #236

Open
FrankKai opened this issue Oct 10, 2020 · 3 comments
Open

express中间件原理connect #236

FrankKai opened this issue Oct 10, 2020 · 3 comments

Comments

@FrankKai
Copy link
Owner

FrankKai commented Oct 10, 2020

express中间件原理connect

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

  • app.use为什么可以添加一个又一个中间件?
  • connect是如何区分普通中间件和错误中间件的?
  • 中间件处理函数中的next指代的又是什么?

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

@FrankKai
Copy link
Owner Author

FrankKai commented Oct 10, 2020

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
Copy link
Owner Author

FrankKai commented Oct 10, 2020

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的解释其实也懂了:

  • 跳过前面的所有错误中间件:index值是递增的,请求只走后面的错误中间件
  • 跳过后面的非异常处理中间件:异常中间件两个条件都满足请求会进入,非异常中间件由于hasError为true因此请求不会进入

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

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
Copy link
Owner Author

中间件处理函数中的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 });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant