现在这个年月再来讨论 express 其实并不讨喜,nodejs 已经不是什么新技术了。虽然 nodejs 还不太成熟,虽然 express 的原作者也已经放弃了 nodejs,虽然nodejs 的包管理机制,callback hell 等问题被人吐槽无数,但还是阻止不了广大的热血青年涌向 nodejs 的怀抱。nodejs 实在是太诱人了,它为后端工程师提供了一种前所未有的"面向回调"的语言. 同时也为广大的前端开发者敞开了一扇金灿灿的大门,一扇通往服务器端开发的大门,然而这并没什么卵用。
前后端程序要解决的问题实际上是截然不同的,如果你认为一个熟悉前端的工程师能够使用 nodejs 轻松驾驭后端的程序,那我只能说你想多了。 前端程序是运行在浏览器里的,要解决的最主要的问题是:用户体验,而主要困难是代码对不同浏览器的兼容性。而后端程序运行在操作系统上,要解决的主要问题是:功能实现、性能、稳定性、扩展性···。 如果你分别找一份前端和后端的代码比较一下,你会发现虽然都是 js,但是除了语法相似外就没什么相似的地方了。
好了,废话不多说了,我们来看看 express.js。
我们来谈谈哲学
express.js 中需要开发者编写的主要是什么 (你是干什么的)?
先来看一下官方的示例:
var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
express中我们最主要的任务是实现 app.get(), app.post() 中的 callback 部分,express 里叫 route handler, 也就是通常我们说的controller.
怎么开始对 request 的处理 (从哪儿来)?
官方文档里告诉你要这么写
app.get('/', function(req, res){
res.send('hello world');
});
app.get 的第二个参数是一个 callback,他接受两个参数,一个是 request,一个是 response。
但是如果你认真看文档,你会发现,app.get定义其实是这样子滴:app.get(path, callback [, callback ...])
, 看见没?可以有多个 callback!那怎么用呢?
// app.get 中的 callback 其实可以接受第三个参数--另一个 callback
// 这个 callback 指向app.get 参数中下一个 route handler(这很 nodejs)
app.get('/', function(req, res, next){
//blabla, 业务逻辑1
//blabla, 业务逻辑1
//blabla, 业务逻辑1
next(); // 调用下面这个 handler
// |
// |
// \|/
}, function(req, res, next){
//blabla, 业务逻辑2
//blabla, 业务逻辑2
//blabla, 业务逻辑2
next(); // 调用下面这个 handler
// |
// |
// \|/
}, function(req, res){
// 最后一个 handler 中无需调用 next
// 构建 response
res.send('hello world');
});
大部分情况下next()
不需要参数, 但是其实它是可以接受参数的, 这里有两种情况:
-
next('route')
可以跳过后面的 route handler. -
next(err)
同样会跳过后续的 route handler, 但是与next('route')
不同的是, 他会触发错误处理的 handler( 后面会提到).
如何结束对 request 的处理(到哪儿去)?
看上去很简单的问题不是吗?
通常你会使用 res.end()
, 高级点的会使用 res.send()
, res.json()
, 甚至是 res.links()
, res.format()
等等. 但是当你调用这些函数之后, 处理过程就结束了吗? 其实没有. 你会发现 res.end()
后面的代码依然会被执行.
那么, 如何才能主动的结束 route handler 的执行呢? 答案就是上面提到的 next()
函数, 但是如果 next() 后面还有代码怎么办? 所以, 比较通用的方法是:
.....
return next(err);
// 后续的代码以及后续的 route handler 都不会被执行.
.....
错误处理
express 中可以定义全局的 error handler, 方法如下:
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
// next(err);
});
error handler 同样可以有多个, 与 route handler 类似的通过 next()
串联, 不同的是: 需要带上 err 参数.
全局 error handler 为工程师提供了极大的方便, 但是实际项目中经常发挥不了作用, 原因只有一个: 对错误处理的漠视
这里摘一个实际项目中的例子:
exports.client=function(callBack){
pool.acquire(function(err,db){ // err 被无情的忽略
var timer=setTimeout(function(){
pool.release(db);
},20000);
db.checkDisconnect=timer;
callBack(db);
});
};
exports.collection=function(db,collectionName,callBack){
db.collection(collectionName,{safe:true},function(err,collection){
if(err) { // 没有将 err 向上传递.
console.log("DBClient-collection-err: " + err);
}
callBack(collection); // 应该改为callBack(err, collection);
});
};
get:function(id,callBack){
var self=this;
dbClient.client(function(db){
dbClient.collection(db,self.DBTable,function(collection){
collection.findOne({'id':id},function(err, doc){
if(err) { // 没有将 err 向上传递.
console.log("DBClient-findOne-err: " + err);
}
if(doc){
delete doc._id;
}
callBack(doc); // 应该改为callBack(err, doc);
dbClient.close(db);
});
});
});
}
exports.add=function(req,res){
var cookie=req.cookies;
var userid=cookie.userid;
// 这里完全忽略了 dao.findOne 中有可能出现的 err !!!
// 一旦 err 发生, 后续对 user 的引用就会发生异常, 而这个时候看到的 err 的位置有可能和实际的 err 位置相距十万八千里. 使错误难以定位.
// 一旦 err 发生, 第一次引用 user 之前的代码, 通通都是无用功, 有可能对程序性能造成影响.
dao.findOne({id:userid},config.dbUser,function(user){
//.....
//.....