假设你有这样一段简单的代码:

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

这个函数有两个参数,req和res,分别表示请求和响应对象。

另一方面,还有其他带有第三个参数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实际在做什么?


当前回答

A bit of internals here. The main purpose of express app handle function is to send a response to the client, and terminate the request-response cycle. And termination of this cycle can be done by one of the response methods (e.g. res.end(), res.json(), etc). Meaning if a middleware or route handler does some actions but then doesn't call one of the response methods or pass the control to the next handler or middleware, the request-response cycle will not be terminated. But what the next does depends on where and how it gets called.

为了管理不同的任务(路由处理程序、中间件),express创建了堆栈。它们看起来像一个任务队列。每个路由器和路由创建自己的任务堆栈;

express应用程序的使用方法将任务(中间件功能)推到路由器的堆栈中。app.get、app.post等在路由器中创建了一个单独的路由(有自己的堆栈,并将路由的实际处理程序推给它),然后将这些路由处理程序包在一个函数中推给路由器。这意味着当路由在路由器堆栈中创建时,就像推送子任务的路由任务(包装器函数)。

// 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');
    }
);

在中间件内部调用next (express建议应用use方法挂载中间件)会让我们到达路由器的下一个路由或中间件,导致中间件(挂载时)被推入路由器堆栈。

Next接受不同的参数。任何不是“route”或“router”的参数都将被视为错误,并将被传递给错误中间件,该中间件必须在所有路由之后挂载,并且有四个参数:

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

字符串'route'作为next的参数将跳过所有剩余的路由处理程序,并为我们提供路由器的下一个路由:

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');
    }
);

String 'router'作为next的参数可以让我们脱离当前路由器:

// 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');
    }
);

其他回答

在理解下面的内容之前,您需要对节点中的请求-响应周期有一些了解,但不需要太详细。 它开始于你对特定资源的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 !!!!");
});

这里我们有两个中间件函数对应相同的路径。但你总能从第一个人那里得到回应。因为它首先挂载在中间件堆栈中,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 !!!!");
});

下一个在这里做什么。是的,你可能会有猜测。如果条件为真,它会跳过第一个中间件函数然后调用下一个中间件函数你会得到"Hello Planet !!!!"响应。

因此,接下来将控制传递给中间件堆栈中的下一个函数。

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

大致如下:-

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

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

在本例中,需要调用这两个中间件函数。 因此,到达第二个中间件函数的唯一方法是调用next();

如果你不打电话给下一个。不要期望自动调用第二个中间件函数。调用第一个函数后,您的请求将被挂起。第二个函数永远不会被调用,您也不会得到响应。

它将控制传递给下一个匹配路由。例如,在您给出的示例中,如果给出了id,您可能会在数据库中查找用户,并将其分配给req.user。

下面,你可以有这样的路线:

app.get('/users', function(req, res) {
  // check for and maybe do something with req.user
});

由于/users/123将首先匹配示例中的路由,因此将首先检查并找到用户123;然后/用户可以对结果做一些事情。

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

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);
});

能够像这样控制流是非常方便的。你可能想让某些页面只对带有admin标志的用户可用:

/**
 * 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');
});

希望这能给你一些启发!

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

Next用于将控制传递给下一个中间件函数。如果不是,请求将被挂起或打开。

A bit of internals here. The main purpose of express app handle function is to send a response to the client, and terminate the request-response cycle. And termination of this cycle can be done by one of the response methods (e.g. res.end(), res.json(), etc). Meaning if a middleware or route handler does some actions but then doesn't call one of the response methods or pass the control to the next handler or middleware, the request-response cycle will not be terminated. But what the next does depends on where and how it gets called.

为了管理不同的任务(路由处理程序、中间件),express创建了堆栈。它们看起来像一个任务队列。每个路由器和路由创建自己的任务堆栈;

express应用程序的使用方法将任务(中间件功能)推到路由器的堆栈中。app.get、app.post等在路由器中创建了一个单独的路由(有自己的堆栈,并将路由的实际处理程序推给它),然后将这些路由处理程序包在一个函数中推给路由器。这意味着当路由在路由器堆栈中创建时,就像推送子任务的路由任务(包装器函数)。

// 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');
    }
);

在中间件内部调用next (express建议应用use方法挂载中间件)会让我们到达路由器的下一个路由或中间件,导致中间件(挂载时)被推入路由器堆栈。

Next接受不同的参数。任何不是“route”或“router”的参数都将被视为错误,并将被传递给错误中间件,该中间件必须在所有路由之后挂载,并且有四个参数:

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

字符串'route'作为next的参数将跳过所有剩余的路由处理程序,并为我们提供路由器的下一个路由:

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');
    }
);

String 'router'作为next的参数可以让我们脱离当前路由器:

// 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');
    }
);