app.get('/500', function(req, res){
throw new Error('keyboard cat!');
});
如下述,我们可以多次调用 app.error()。这里我们检测 NotFound 的实例,并显示 404 页面,或者传到 next 错误处理器。值得注意的是这些处理器可以在任何地方定义,因为他们将会在 listen() 的时候被放置于路由处理器下面。它允许在 configure() 块内有定义,以便我们能基于环境用不同的异常处理方式。
复制代码 代码如下:
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.jade');
} else {
next(err);
}
});
为求简洁(for the simplicity),这里我们假定这个 demo 的所有错误为 500,当然你可以可以选择自己喜欢的。像 node 执行文件系统的系统调用时,你可能会接收到一个带有 ENOENT 的 error.code,意思为 “不存在这样的文件或目录” 的错误,我们可以在错误处理器中使用,或者当有需要时可显示一个指定的页面。
复制代码 代码如下:
app.error(function(err, req, res){
res.render('500.jade', {
error: err
});
});
我们的 app 同样可以利用 Connect 的 errorHandler 中间件来汇报异常。譬如当我们希望在 “开发” 环境输出 stderr 异常时,我们可以使用:
复制代码 代码如下:
app.use(express.errorHandler({ dumpExceptions: true }));
同时在开发阶段我们可能需要在花哨的 HTML 页面显示我们传递和抛出的异常,对此我们可以把 showStack 设置为 true。
复制代码 代码如下:
app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
errorHandler 中间件还可以在 Accept: application/json 存在的时候返回 json,这对于开发重度依赖客户端 Javascript 的应用非常有用。
十三、Route 参数预处理
路由参数预处理,通过隐式数据加载和请求验证,可以大大提升你程序的可读性。打个比方,你通常需要持续地从多个路由获取基本数据。像用 /user/:id 加载一个用户,通常来说我们可能会这样干:
复制代码 代码如下:
app.get('/user/:userId', function(req, res, next){
User.get(req.params.userId, function(err, user){
if (err) return next(err);
res.send('user ' + user.name);
});
});
通过预处理,我们的参数可以映射到执行验证、控制(coercion),甚至从数据库加载数据的回调。如下我们带着参数名调用 app.param() 希望将其映射于某些中间件。如你所见,我们接受代表占位符值的 id 参数。使用这个,我们如常加载用户并处理错误,以及简单地调用 next() 来把控制权交由下一个预处理或者路由处理器。
复制代码 代码如下:
app.param('userId', function(req, res, next, id){
User.get(id, function(err, user){
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
一旦这样做,上所述将会大大地提升路由的可读性,并且允许我们轻松地在整个程序中共享逻辑:
复制代码 代码如下:
app.get('/user/:userId', function(req, res){
res.send('user ' + req.user.name);
});
十四、View 处理
View 文件件使用 <name>.<engine> 这样的格式,其中 <engine> 是被 require 进来模块的名。例如 layout.ejs 将告诉 view 系统去 require('ejs'),被加载的模块必须(导出) exports.compile(str, options) 方法,并返回一个 Function 来适应 Express。app.register() 可用以改变这种默认行为,将文件扩展名映射到特定的引擎。譬如 “foo.html” 可以由 ejs 来处理。
下面这个例子使用 Jade 来处理 index.html。因为我们并未使用 layout: false,index.jade 处理后的内容将会被传入到 layout.jade 中一个名为 body 的本地变量。
复制代码 代码如下: