















一、概述
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",客户端请求挂起。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。