現在這個年月再來討論 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){
//.....
//.....