Express中的参数“next”用于什么?

384

假设您有如下简单的代码块:

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

这个函数有两个参数,reqres,分别代表请求和响应对象。

另外,还有其他带有第三个参数叫做next的函数。比如,我们来看一下下面的代码:

app.get('/users/:id?', function(req, res, next){ // Why do we need next?
    var id = req.params.id;
    if (id) {
        // do something
    } else {
        next(); // What is this doing?
    }
});

我不明白使用 next() 的意义或者为什么要使用它。在那个例子中,如果id不存在,next 到底有何作用?


17
接下来的操作是简单地允许排在后面的路由处理程序来处理请求。在这种情况下,如果用户ID存在,则可能使用res.send来完成请求。如果不存在,则很可能有另一个处理程序会发出错误并完成请求。 - Dominic Barnes
1
那么你的意思是说,如果我在 app.get('/users',function(req,res)) 后面有一个 app.post('/login',function(req,res)),通过调用 next() 它将调用 login 作为 app.js 文件中的下一个路由? - Menztrual
4
基本上,下一个要执行的路由将是匹配请求 URL 的另一个路由。在这种情况下,如果通过 app.get("/users") 注册了另一个路由,则如果上面的处理程序调用 next,则将运行该路由。 - Dominic Barnes
3
下一个基本上只是回调函数。 - Jonathan Ong
3
知道一下,这被称为“next()”,只是一个惯例,但它可以被称为任何可能的名称。 - KhoPhi
6个回答

315
它将控制权传递给下一个匹配的路由。例如,在您提供的示例中,如果给定了id,则可以在数据库中查找用户并将其分配给req.user。
下面,您可以有这样一个路由:
app.get('/users', function(req, res) {
  // check for and maybe do something with req.user
});

由于在你的示例中,/users/123将首先匹配路由,因此它将首先检查并找到用户123;然后/users可以对该结果进行操作。

路由中间件是一种更灵活、更强大的工具,因为它不依赖于特定的URI方案或路由排序。在我看来,我倾向于像这样建模示例,假设有一个带有异步findOne()Users模型:

function loadUser(req, res, next) {
  if (req.params.userId) {
    Users.findOne({ id: req.params.userId }, function(err, user) {
      if (err) {
        next(new Error("Couldn't find user: " + err));
        return;
      }

      req.user = user;
      next();
    });
  } else {
    next();
  }
}

// ...

app.get('/user/:userId', loadUser, function(req, res) {
  // do something with req.user
});

app.get('/users/:userId?', loadUser, function(req, res) {
  // if req.user was set, it's because userId was specified (and we found the user).
});

// Pretend there's a "loadItem()" which operates similarly, but with itemId.
app.get('/item/:itemId/addTo/:userId', loadItem, loadUser, function(req, res) {
  req.user.items.append(req.item.name);
});

像这样控制流程非常方便。您可能希望仅将某些页面提供给具有管理员标志的用户:

/**
 * Only allows the page to be accessed if the user is an admin.
 * Requires use of `loadUser` middleware.
 */
function requireAdmin(req, res, next) {
  if (!req.user || !req.user.admin) {
    next(new Error("Permission denied."));
    return;
  }

  next();
}

app.get('/top/secret', loadUser, requireAdmin, function(req, res) {
  res.send('blahblahblah');
});

希望这能给您一些灵感!

9
有时候你会返回 next(),但有时候却不会。 - user626776
9
@John: 实际上返回值被忽略了;我只是想返回那里以确保我不会再次调用 next()。如果我只使用 next(new Error(…)); return;,结果将是相同的。 - Asherah
4
@level0:返回值被忽略,您可以将其视为“next(new Error(...)); return;”的简写。如果我们向“next”传递一个值,则它会被单方面视为错误。我没有深入研究过express代码,但是挖掘一下,您会找到所需的内容 :) - Asherah
2
@level0:(我已将return next(…);更改为next(…); return;,以使其更少令人困惑。) - Asherah
考虑到这已经是一个被高度评价的已接受答案,也许你可以包括一个调用 next('route') 的例子?这基本上会绕过一个处理程序中剩余的回调函数,但将控制传递给具有匹配路由的下一个处理程序。 - zedjay72
显示剩余13条评论

127

我也曾经不理解next(),但是这篇文章帮了我

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

app.get("/", function(httpRequest, httpResponse, next){
    httpResponse.write("Hello");
    next(); //remove this and see what happens 
});

app.get("/", function(httpRequest, httpResponse, next){
    httpResponse.write(" World !!!");
    httpResponse.end();
});

app.listen(8080);

7
非常简洁明了!谢谢!但是你如何确保调用第一个.get而不是第二个呢? - JohnnyQ
33
@JohnnyQ,它将按从上到下的顺序执行。 - Tapash
@JohnnyQ 路由按照它们声明的方式被捕获。如果有两个匹配的路由,程序流程中首先遇到的那个将被选择。 - Shubham Anand
3
@JohnnyQ 谢谢!我只是需要有人,在我读的所有文档中,简单地说一下“自顶向下执行”。多简单啊!这似乎很明显,但当你正在学习这些东西时,没有什么是显而易见的,这真的让我困扰。 - Nathaniel Hoyt

110

在理解next之前,你需要对Node中的请求-响应循环有一些基本概念,尽管不需要非常详细。

它始于你发出一个HTTP请求来获取某个资源,并在你向用户发送响应时结束,即当你遇到像res.send('Hello World')这样的内容时。

让我们看一个非常简单的例子。

app.get('/hello', function (req, res, next) {
  res.send('USER')
})

这里不需要使用 next(),因为 resp.send 会结束请求并将控制权交回路由中间件。

现在让我们看另一个例子。

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

app.get('/hello', function (req, res, next) {
  res.send("Hello Planet !!!!");
});

这里我们为相同路径添加了2个中间件函数,但您将始终从第一个中间件函数获取响应。因为它在中间件堆栈中最先挂载,并且res.send将结束该周期。

但是如果我们总是不想要“Hello World !!!!”的响应呢? 对于某些条件,我们可能希望得到“Hello Planet !!!!”的响应。 让我们修改上面的代码并查看会发生什么。

app.get('/hello', function (req, res, next) {
  if(some condition){
    next();
    return;
  }
  res.send("Hello World !!!!");  
});

app.get('/hello', function (req, res, next) {
  res.send("Hello Planet !!!!");
});

这里为什么有next呢?你可能已经猜到了。如果条件成立,它将跳过第一个中间件函数并调用下一个中间件函数,然后您将得到"Hello Planet !!!! "的响应。

所以,next将控制权传递给中间件堆栈中的下一个函数。

如果第一个中间件函数没有返回任何响应但执行了一段逻辑,然后您从第二个中间件函数得到响应怎么办?

就像下面这样:

app.get('/hello', function (req, res, next) {
  // Your piece of logic
  next();
});

app.get('/hello', function (req, res, next) {
  res.send("Hello !!!!");
});

在这种情况下,您需要同时调用中间件函数。因此,仅通过调用next()才能到达第二个中间件函数。

如果您不调用next,不要指望第二个中间件函数会自动被调用。在调用第一个函数后,您的请求将被搁置。第二个函数将永远不会被调用,您也将无法收到响应。


@ruffin 是的,你可以把 next 看作是类似于 goto 的东西。但是 next 知道去哪里,而 goto 需要一个标签。Next 将控制权传递给下一个中间件函数。此外,你可以将 'next' 命名为任何你喜欢的名称。这里只是一个标签。但最好的做法是使用名称 'next'。 - Mav55
5
好的,看起来这并不准确。我尝试了这段代码(pastebin here),在next()调用之后,代码确实是被执行了。在这种情况下,past the next() call将被写入控制台,然后当第二个res.send被调用时,我会收到一个Error: Can't set headers after they are sent.错误消息,尽管没有成功发送。在next()调用之后,代码流确实返回,这使得@Ashe的returns(或其他逻辑管理)非常重要。 - ruffin
7
@ruffin,是的,你说得对。在next()后面需要一个返回语句来跳过剩余语句的执行。感谢指出这点。 - Mav55
3
谢谢你详细解释“中间件”是什么、做什么以及提供清晰的例子,而不是简单地重复文档。这是唯一一个清楚说明发生了什么、为什么以及如何发生的答案。 - mc01
我不理解的是为什么我们不能在第一个中间件中完成逻辑并依赖于另一个中间件? - mdehghani
显示剩余3条评论

20

Next 用于将控制权传递给下一个中间件函数。如果不使用 Next,则请求将被挂起或保持打开状态。


7
我不确定这个答案对一个将近七年的问题有什么贡献... - mherzig
3
虽然简短,但这个评论引导我到了这里:https://expressjs.com/zh-cn/guide/writing-middleware.html - makstaks
2
@mherzig,这是一行代码,涵盖了所有内容。 - M. Gopal

4
一些内部细节。 express app handle 函数的主要目的是向客户端发送响应,并终止 请求-响应周期。而这个周期的终止可以通过其中一个 响应方法(如 res.end()、res.json() 等)来完成。也就是说,如果中间件或 路由处理程序 执行了某些操作,但没有调用其中一个 响应方法 或将控制权传递给下一个处理程序或中间件,则 请求-响应周期 将不会被终止。但 next 的作用取决于它在何处以及如何调用。
为了管理不同的任务(路由处理程序、中间件),express 创建了 堆栈。它们看起来像是一系列 任务队列。每个 路由器路由 都会创建自己的 任务堆栈express appuse 方法将 任务中间件 函数)推送到 路由器堆栈 中。app.getapp.post 等会在 路由器 中创建一个单独的 路由(带有自己的 堆栈),并将该 路由 的实际 处理程序 推送到其中,然后将这些 路由 处理程序包装在一个函数中推送到 路由器 中。也就是说,当在 路由器 中创建一个 路由 时,会将类似于 route task(包装函数)与推送的 子任务 放入其中。
// pushes task to the router stack
app.use((req, res, next) => {
    console.log('log request');
    next();
});

// creates route, route stack, 
// pushes tasks to the route stack,
// wraps tasks in a function (another task)
// pushes wrapper function to the
// router stack
app.get('/', (req, res, next) => {
    res.send('Hello World');
});

作为一个路由,它有自己的堆栈。如果没有参数调用next方法,我们只能到达路由的下一个处理程序:
app.get('/', 
    (req, res, next) => {
        console.log('first handler');
        // passes the control to the second handler
        next();
    },
    (req, res, next) => {
        console.log('second handler');
        res.send('Hello World');
    }
);

middleware中调用nextexpress建议使用use方法来挂载middleware)会将我们带到router的下一个routemiddleware,因为已经挂载的middleware会被推入到routerstack中。

next接受不同的参数。任何不是'route''router'的参数都会被视为错误,并传递给errormiddleware,该middleware必须在所有路由之后挂载,并具有四个参数:

// error handling middleware
app.use((error, req, res, next) => {
    res.status(error.status || 500);
    res.send(error.message || 'Server internal error');
});

使用'route'作为next的参数将跳过所有剩余的route handlers并让我们进入router的下一个route

app.get('/', 
    (req, res, next) => {
        console.log('first handler');
        // passes control to the next route
        next('route');
    },
    (req, res, next) => {
        // this handler will be skipped
        next();
    }
);

app.get('/',
    (req, res, next) => {
        // this route will be called at the end
        res.send('Hello World');
    }
);

next函数中使用字符串参数'router'可以使我们退出当前的router

// router 'one'
app.get('/', 
    (req, res, next) => {
        console.log('first handler');
        // passes control to the next router
        next('router');
    },
    (req, res, next) => {
        // this handler will be skipped
        next();
    }
);

// router 'two'
app.get('/',
    (req, res, next) => {
        // this route will be called at the end
        res.send('Hello World');
    }
);

你是不是想在这里写第二个处理程序 here - Daniyal Nasir
1
谢谢你,伙计!对于我这个新手来说,你的解释帮我澄清了很多问题(关于Node)。 - Daniyal Nasir

0
我还想补充一下为什么express不调用下一个中间件并让我们控制它。由于Node是异步的,如果Express在等待某些异步调用完成之前调用下一个中间件,响应可能是不完整或包含错误,因此用户可以控制何时调用下一个中间件函数。

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