Express 中的 next

const app = require("express")()

app.use((req, res, next) => {
  console.log(1);
  next();
  console.log(6);
})

app.use((req, res, next) => {
  console.log(2);
  next();
  console.log(5);
})

app.use((req, res, next) => {
  console.log(3);
  next();
  console.log(4);
})

app.use((req, res, next) => {
  res.send('Hello Express');
});

app.listen(3000);
// 输出 1 2 3 4 5 6

注册

Express 中使用 use 函数注册中间件, 并按照注册顺序调用
注册时最终会调用 lib/router/index.js use 函数 最终会 new 一个 Layer 并放入 stack 队列中

proto.use = function use(fn) {
  ...
  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: false,
    end: false
  }, fn);

  layer.route = undefined;
  this.stack.push(layer);
  ...
}

调用

在收到请求时的处理在 lib/router/index.js handle
然后在 handle 函数中定义了 next 函数
然后执行 next 函数去到 lib/router/layer.jshandle_request 函数或者 handle_error 函数

// lib/router/index.js handle 极度简化了代码
proto.handle = function handle(req, res, out) {
  ...
  idx = 0
  stack = this.stack

  next()

  function next(err) {
    ...
    if (idx >= stack.length) {
      return;
    }
    ...
    layer = stack[idx++]
    ...
    if (err) {
      layer.handle_error(err, req, res, next);
    } else {
      layer.handle_request(req, res, next);
    }
  }
  ...
}
Layer.prototype.handle_error = function handle_error(error, req, res, next) {
  var fn = this.handle;

  if (fn.length !== 4) {
    // not a standard error handler
    return next(error);
  }

  try {
    fn(error, req, res, next);
  } catch (err) {
    next(err);
  }
};

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};

从上面的 handle 可以看出, 通过 next 的嵌套调用实现中间件的调用
即调用 next 就会调用下一个中间件, 但相比 Koa 它并不支持 Promise
并且使用 try catch 捕捉了错误, 通过 next 进行传播, 所以错误中间件必须在最后注册

简化版本

const stack = [];

function use(fn) {
    stack.push(new Layer(fn));
}

function handle() {
    let idx = 0;
    
    next()

    function next(err) {
        if (idx >= stack.length) {
            return;
        }
        const layer = stack[idx++]
        if (err) {
            layer.handle_error(err, next)
        } else {
            layer.handle_request(next)
        }
    }
}
function Layer(fn) {
    this.handle = fn
}
Layer.prototype.handle_error = function (err, next) {
    const fn = this.handle
    if (fn.length !== 2) return next(err) // 跳过不是错误处理的中间件
    try {
        fn(err, next)
    } catch (err) {
        next(err)
    }
}

Layer.prototype.handle_request = function (next) {
    const fn = this.handle
    if (fn.length > 1) return next() // 跳过错误处理的中间件
    try {
        fn(next)
    } catch (err) {
        next(err)
    }
}


use((next) => {
    console.log(1);
    next();
    console.log(6);
})
use((next) => {
    console.log(2);
    next();
    console.log(5);
})
use((next) => {
    console.log(3);
    next();
    console.log(4);
})
use((next) => {
    throw new Error("some err");
})
use((err, next) => {
    console.log(err.message)
})
handle()
/**
 * 输出
 * 1
 * 2
 * 3
 * some err
 * 4
 * 5
 * 6
 **/

结论

  • 中间件代码中必须调用 next() 方法, 后续中间件才会执行
  • 中断剩余中间件代码只要不调用 next() 方法即可
  • 普通中间件参数不能大于3个, 错误处理中间件参数只能是4个, 否则将不会被调用
  • 错误处理中间件必须要排在普通中间件的后面
  • 中间件不支持 Promise
  • 中间件调用 next() 方法之后, 会先执行后面的中间件最后再执行当前中间件的剩余代码, 即使是在中间件抛出错误时也一样