|
一、概述
Express.js所谓中间件,就是从接收到用户http请求开始到调用响应对象(res)之间的那段处理过程,它的本质是一个类似这样的回调函数:
其中,req是请求对象,res是响应对象,next则是一个函数,调用它可以马上跳到下一个中间件回调函数。
一个匹配的路由可以存在很多个中间件,来作为该用户请求的处理过程,直至最终调用响应对象输出响应内容给用户,这个过程就要用到next函数:
app.use((req, res, next) => {
console.log("pos_01");
next();
});
app.use((req, res, next) => {
console.log("pos_02");
next();
});
app.get('/', (req, res, next) => {
console.log("pos_03");
res.send('Hello World');
});
当用户访问站点根目录,以上代码首先会执行第一个app.use传入的“中间件”,再跳到第二个app.use传入的“中间件”,最后才执行app.get绑定的"中间件",在此中间件当中不再调用next(),所以它是处理请求的终点,在此调用响应对象输出响应内容给用户:
//服务器输出
pos_01
pos_02
pos_03
//浏览器输出
Hello World
总之,一个指定了多个中间件的路由,这些中间件当中,如果不指定next(),它是不会主动跳到下一个中间件的,执行完毕的中间件如果没有调用res.send()之类的方法,请求就会挂起:
route.use((req, res, next) => {
console.log("pos_01");
});
route.get('/', (req, res, next) => {
console.log("pos_02");
res.send('bird home page.');
});
由于第一个中间件不调用next(),所有符合此路由之下的剩余中间件都不会执行;
无论是任何方法调用的中间件都沿用该逻辑,以下两个例子都是执行了第一个中间件后,后续的路由即使符合匹配规则,也不会执行:
route.get('/', (req, res, next) => {
console.log("pos_01");
});
route.get('/', (req, res, next) => {
//不会执行
console.log("pos_02");
});
const midd_1 = (req, res, next) => {
console.log("pos_01");
};
const midd_2 = (req, res, next) => {
//不会执行
console.log("pos_02");
}
route.get('/', [midd_1, midd_2]);
route.get('/', (req, res, next) => {
//不会执行
console.log("pos_03");
});
二、next不会终止当前函数栈,只是纯粹调用了下一个函数(return的重要性)
在回调函数中调用next()并不会影响该函数流程,它只是纯粹进入了另外一个函数栈,当它返回,仍会继续执行剩下的内容:
app.use((req, res, next) => {
console.log("pos_01");
next();
console.log("pos_011");
});
app.use((req, res, next) => {
console.log("pos_02");
next();
});
app.get('/', (req, res, next) => {
console.log("pos_03");
res.send('Hello World');
});
留意第一个app.use传入的“中间件”,它在调用next()后依然有后续逻辑,这些逻辑会等待next执行玩其他"中间件"后继续执行:
//服务器输出:
pos_01
pos_02
pos_03
pos_011
//浏览器输出:
Hello World
所以,我们应该在需要调用next()跳过当前中间件函数的后面加上return语句,防止它继续往后执行:
app.use((req, res, next) => {
console.log("pos_01");
next();
return;
console.log("pos_011");
});
在第一个app.use绑定的中间件处加上return,就能确保"pos_011"不会再输出;
三、next(param)
next()函数接受一个参数:
1.这个参数可以是一个Error对象,当参数为Error对象,传入这个对象的next()的行为将不是调用下一个"中间件"而是直接抛出错误并返回:
app.use((req, res, next) => {
console.log("pos_01");
next();
console.log("pos_011");
});
app.use((req, res, next) => {
console.log("pos_02");
next(new Error('failed to load user'));
});
app.get('/', (req, res, next) => {
console.log("pos_03");
res.send('Hello World');
});
以上例子的app.get绑定的回调函数将不会执行,浏览器会输出来自next()的错误信息,但是"pos_011"依然会输出,因为next()接受的错误对象不中断流程的执行(这也许是因为回调函数在不同的tick的原因):
//服务器输出:
pos_01
pos_02
pos_011
Error: failed to load user
at file:///mnt/hgfs/lroot/wwwroot/10003/test001/index04.js:19:8
...
//浏览器输出:
Error: failed to load user
at file:///mnt/hgfs/lroot/wwwroot/10003/test001/index04.js:19:8
...
2. next('route'),当参数为'route',next()的行为是跳过当前路由set的所有中间件,从下一个符合条件的路由继续(如果有的话):
//第一个路由定义的一set中间件
route.get('/', (req, res, next) => {
console.log("pos_01");
next('route');
return;//记得加上return,防止后续路由运行完毕后返回运行此路由剩下的上下文
console.log("pos_02");
}, (req, res, next) => {
console.log("pos_03");
});
//第二个路由定义的中间件
route.get('/', (req, res, next) => {
res.send('bird home page.');
console.log("pos_04");
});
Express将每个编写的app.METHOD(),route.METHOD()定义为一个独立的中间件set,这些方法的第二个以上参数,可以接受一个或多个函数作为只指定一个中间件,或指定一个数组,数组每一项都是函数作为指定多个中间件,总之,只要是指定了多个中间件函数的方法,都认为是中间件set,next('route')就是用来跳过这些中间件set的;
以上示例中,第一个route.METHOD()指定了中间件set,在第一个中间件执行到next('route')后,会直接跳过此数组剩余的中间件函数,直接去执行下一个route.METHOD()指定的中间件,所以"pos_03"是不会输出的,同时又因为加上了return,所以"pos_02"也不会输出,原因参考上文;
注意事項
- 僅限路由回呼:
next('route') 只在透過 app.METHOD() 或 router.METHOD() 載入的中間件函式中有效。
- 不要在
app.use() 使用:在 app.use() 中呼叫 next('route') 的效果等同於普通的 next(),不會跳過後續的中間件。
三、指定多个"中间件"但没调用next()
当某个路由指定了多个中间件,但是中途其中一个中间件没有调用next(),那么它将不会自动切换到下一个中间件,结果就是执行完当前中间件后挂起请求(如果这个中间件最终也没有调用响应对象输出内容的话):
const cb0 = function (req, res, next) {
console.log('CB0')
//正常情况会调用next(),但是此处故意不调用
//next()
}
const cb1 = function (req, res, next) {
console.log('CB1')
next()
}
const cb2 = function (req, res) {
res.send('Hello from C!')
}
app.get('/', [cb0, cb1, cb2]);
结果就是,服务器输出"CB0",客户端请求挂起。
来源:https://www.cnblogs.com/yiyide266/p/19061166 |