Express的next函数,它真正的作用是什么?

177

一直在寻找next()方法的良好描述。 在Express文档中,它说可以使用next('route')跳转到该路由并跳过其中所有路由,但有时会调用无参数的next。 有人知道描述next函数的良好教程吗?

9个回答

212

next()函数不带参数时表示“开玩笑,我其实不想处理这个”。它将重新寻找下一个匹配的路由。

这是有用的,例如如果您想要拥有一些具有URL别名的页面管理器以及许多其他东西,这里是一个例子。

app.get('/:pageslug', function(req, res, next){
  var page = db.findPage(req.params.pageslug);
  if (page) {
    res.send(page.body);
  } else {
    next();
  }
});

app.get('/other_routes', function() {
  //...
});

这段代码应该会检查一个特定id slug的页面是否存在于数据库中。如果找到了,就呈现它!如果没有找到,则忽略此路由处理程序并检查其他路由。

因此,使用没有参数的next()可以让您假装您没有处理路由,以便其他东西可以取而代之。


或者使用app.all('*')来实现点击计数器。这允许您执行一些共享设置代码,然后继续移动到其他路由以执行更具体的操作。

app.all('*', function(req, res, next){
  myHitCounter.count += 1;
  next();
});

app.get('/other_routes', function() {
  //...
});

1
很棒的答案! 我还看到了一些其他使用next的方式,例如next(err)和next('route')。您知道这些的目的是什么,何时需要传播错误,何时需要跳转到特定路由? - Andreas Selenwall
11
next('route') 是特定于 app.VERB() 的,用于当一个路由有多个回调函数时,"绕过剩余的路由回调函数。" next(err) 用于跳转到任何"错误中间件"(来自文章中的 E)。 - Jonathan Lonowski
8
点赞。就是因为这句话:“只是开玩笑,我真的不想处理这个”的存在,让我明白了我在寻找什么。我看到了很多类似的问题,但在这一句中找到了解决方案。 - Pedro Barros
13
不带参数调用next()并不是告诉系统“我不想处理这个请求”,而是告诉系统在完成此中间件后继续处理任何剩余的中间件。调用next()并不是错误条件,它是正常流程的一部分。如果你不调用next(),则不会处理其他路由。 - d512
2
我希望这个网站上更多的答案是为外行人写的... 'next()没有参数表示“开玩笑,我实际上不想处理这个”' - 对于新手来说非常完美...(@d512有一个更好的解释) - daCoda

152

在大多数框架中,你会收到一个请求并希望返回一个响应。由于Node.js的异步特性,如果你正在进行一些非平凡的事情,你会遇到嵌套回调的问题。为了防止这种情况发生,Connect.js(在v4.0之前,Express.js是基于connect.js的一层)有一个被称为中间件的东西,它是一个具有2、3或4个参数的函数。

function (<err>, req, res, next) {}

你的 Express.js 应用程序是这些函数的堆栈。

enter image description here

路由器是特殊的中间件,它让您执行某个URL的一个或多个中间件。因此它是一个嵌套在堆栈内部的堆栈。

那么 next 做什么呢?简单,它告诉您的应用程序运行下一个中间件。但当您向 next 传递一些内容时会发生什么呢?Express 将会中止当前堆栈,并运行所有具有 4 个参数的中间件

function (err, req, res, next) {}

这个中间件用于处理任何错误。我喜欢采取以下步骤:

next({ type: 'database', error: 'datacenter blew up' });

遇到这种错误,我会向用户提示出现了问题,并记录实际错误。

function (err, req, res, next) {
   if (err.type === 'database') {
     res.send('Something went wrong user');
     console.log(err.error);
   }
};
如果你把 Express.js 应用程序看作一个堆栈,那么你可能能够自己解决很多奇怪的问题。例如,当你在路由器之后添加 Cookie 中间件时,你的路由就不会有 cookie,这是有道理的。

文档

你可以像其他中间件一样定义错误处理中间件,但需要使用四个参数而非三个,具体签名为 (err, req, res, next):

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

3
你会把这个匿名记录函数存储在哪里? - dennismonsewicz
2
"Express将中止当前堆栈,并运行具有4个参数的所有中间件。我想知道Express如何能够运行精确的错误处理程序中间件。谢谢!" - Abhishek Agarwal
@AbhishekAgarwal有完全相同的疑问,很好的综合。 - user12582392

133

依我之见,对于这个问题而言,接受的答案并不真正准确。就像其他人所说的那样,它实际上是关于控制链中下一个处理程序何时运行的。但我想提供更多的代码来使它更加具体化。比如,假设你有一个简单的Express应用程序:

var express = require('express');
var app = express();

app.get('/user/:id', function (req, res, next) {
    console.log('before request handler');
    next();
});

app.get('/user/:id', function (req, res, next) {
    console.log('handling request');
    res.sendStatus(200);
    next();
});

app.get('/user/:id', function (req, res, next) {
    console.log('after request handler');
    next();
});

app.listen(3000, function () {
    console.log('Example app listening on port 3000!')
});

如果您这样做

curl http://localhost:3000/user/123

你将看到这个打印到控制台:

before request handler
handling request
after request handler
现在,如果您将中间处理程序中对next()的调用注释掉,如下所示:
app.get('/user/:id', function (req, res, next) {
    console.log('handling request');
    res.sendStatus(200);
    //next();
});
您将在控制台上看到这个:
before request handler
handling request

请注意,最后一个处理程序(打印after request handler的处理程序)未运行。这是因为您不再告诉Express运行下一个处理程序。

所以,无论您的“主”处理程序(返回200的处理程序)是否成功,如果您希望其余中间件继续运行,则必须调用next()

什么情况下会派上用场?假设您想将所有进入某个数据库的请求记录到日志文件中,而不管请求是否成功。

app.get('/user/:id', function (req, res, next) {
    try {
       // ...
    }
    catch (ex) {
       // ...
    }
    finally {
       // go to the next handler regardless of what happened in this one
       next();
    }
});

app.get('/user/:id', function (req, res, next) {
    logToDatabase(req);
    next();
});

如果您想运行第二个处理程序,您需要在第一个处理程序中调用next()

请记住,Node是异步的,所以它不知道第一个处理程序的回调函数何时完成。您需要通过调用next()告诉它。


Sonfar对我来说是最好的答案。 - Norayr Ghukasyan
1
很好的解释! - Chang
我喜欢这个答案和你的描述。谢谢。 - ORCAs
这是关于编程的内容,请将其翻译为中文。请仅返回翻译后的文本:这应该是期望的答案。清晰、简洁、详尽! - benwl

14

不带参数的next()会调用框架中的下一个路由处理程序或下一个中间件。


4
或者下一个中间件。 - robertklep

11

总结正确提到的答案并放在一个地方:

  • next():在同一路由中,控制权转移到下一个函数。在单个路由中有多个函数的情况下使用。
  • next('route'):通过跳过当前路由中所有剩余的函数,将控制权转移到下一个路由。
  • next(err):将控制权移交给错误中间件。
app.get('/testroute/:id', function (req, res, next) {
  if (req.params.id === '0') next() // Take me to the next function in current route
  else if (req.params.id === '1') next('route') //Take me to next routes/middleware by skipping all other functions in current router  
  else next(new Error('Take me directly to error handler middleware by skipping all other routers/middlewares'))
}, function (req, res, next) {
  // render a regular page
  console.log('Next function in current route')
  res.status(200).send('Next function in current route');
})

// handler for the /testroute/:id path, which renders a special page
app.get('/testroute/:id', function (req, res, next) {
  console.log('Next routes/middleware by skipping all other functions in current router')
  res.status(200).send('Next routes/middleware by skipping all other functions in current router');
}) 
//error middleware
app.use(function (err, req, res, next) {
  console.log('take me to next routes/middleware by skipping all other functions in current router')
  res.status(err.status || 500).send(err.message);
});


谢谢您的解释,非常快速且真正有帮助 :) - Ale Ortega

4

这个问题也问到了next('route')的使用,目前提供的答案中似乎已经涵盖了:

  1. next()函数的用法:

简单来说:是下一个中间件函数。

从官方Express JS文档 - 'writing-middleware'页面中摘录如下:

"myLogger中间件函数只是打印一条消息,然后通过调用next()函数将请求传递给堆栈中的下一个中间件函数。"

var express = require('express')
var app = express()

var myLogger = function (req, res, next) {
  console.log('LOGGED')
  next()
}

app.use(myLogger)

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000)

Express JS文档中指出:"如果当前的中间件函数没有结束请求-响应循环,那么它必须调用next()将控制权传递给下一个中间件函数。否则,请求将被挂起。"

  1. 使用next('route'):

简单来说:next route(与next()中的下一个中间件函数相对)

从这个 Express JS文档-“使用中间件”页面中提取:

“要跳过路由器中间件堆栈中其余的中间件函数,请调用next('route')将控制权传递给下一个路由。注意:next('route')仅在使用app.METHOD()或router.METHOD()函数加载的中间件函数中有效。”

此示例显示了一个中间件子堆栈,用于处理对/user/:id路径的GET请求。

app.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next route
  if (req.params.id === '0') next('route')
  // otherwise pass the control to the next middleware function in this stack
  else next()
}, function (req, res, next) {
  // render a regular page
  res.render('regular')
})

// handler for the /user/:id path, which renders a special page
app.get('/user/:id', function (req, res, next) {
  res.render('special')
})

1
next('route')的解释很好。 这个答案被低估了,应该有更多的投票。 - Yogesh Umesh Vaity

2

注意上面的next()调用。调用此函数会在应用程序中调用下一个中间件函数。next()函数不是Node.js或Express API的一部分,而是传递给中间件函数的第三个参数。next()函数可以命名为任何名称,但按照惯例,它总是命名为“next”。为避免混淆,请始终使用此约定。


2
这只是简单地将控制权传递给下一个处理程序。
干杯。

0

next() 是回调参数,用于中间件函数与 req 和 res 作为 http 请求和响应参数传递给下面的代码中的 next。

app.get('/', (req, res, next) => { next() });

因此,next() 调用传入的中间件函数。如果当前中间件函数没有结束请求-响应周期,则应调用 next(),否则请求将被挂起并超时。

当多个中间件函数传递给 app.use 或 app.METHOD 时,需要在每个中间件函数内调用 next() fn,否则下一个中间件函数将不会被调用(如果传递了多个中间件函数)。要跳过调用剩余的中间件函数,请在中间件函数内调用 next('route'),之后不应再调用其他中间件函数。在下面的代码中,fn1 将被调用,fn2 也将被调用,因为在 fn1 中调用了 next()。但是,由于在 fn2 中调用了 next('route'),因此不会调用 fn3。

app.get('/fetch', function fn1(req, res, next)  {
console.log("First middleware function called"); 
    next();
}, 
function fn2(req, res, next) {
    console.log("Second middleware function called"); 
    next("route");
}, 
function fn3(req, res, next) {
    console.log("Third middleware function will not be called"); 
    next();
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接