惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
月光博客
月光博客
T
Tailwind CSS Blog
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
罗磊的独立博客
Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
量子位
雷峰网
雷峰网
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队
博客园 - 聂微东
V
V2EX

博客园 - yiyide266

[css]浏览器默认样式 have的实质 This comes after Chinese authorities announced an investigation against Mayday, in response to a viral video. [Linux]终端ssh远程连接如何保持 [PHP]PDO的dsn对mysql的连接影响 [PHP]回调函数参数(callable类型)的一些细节 [javascript]端序(endian)和Buffer对象的read|write系列函数 vscode处理HTML标签两个光标的问题 intend,intent和indent [css]一个块格式化上下文(BFC)阻止外边距重叠的示例 [PHP_yaf]内部注册到spl_autoload的方法 [PHP_yaf]Yaf_Application [PHP_yaf]写在用户代码的配置项 [PHP_yaf]保存在php.ini中的配置项 [PHP]spl_autoload函数栈的向下遍历机制 [英语单词]关于战争 [MySql]1820错误码重置密码方法 [Linux]信号捕捉函数总是第一时间执行 [Linux]信号捕捉函数与exec
[Express.js]next()函数的作用
yiyide266 · 2026-03-12 · via 博客园 - yiyide266

一、概述

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",客户端请求挂起。