請關(guān)注專題:我的NodeJS學習之路(實踐之路)
小弟初涉node領(lǐng)域,不足之處,還請多多指教!
歡迎Star、Fork:https://github.com/gefangshuai/ANodeBlog
今天是不幸的一天,為什么說呢,因為Github掛了!全球最大的同性交友網(wǎng)站掛了,讓我等技術(shù)宅還怎么好好的擼代碼呢?
好了,閑篇少扯,說點正事吧。今天我們來介紹程序中用到的幾個強大的中間件。
async - 強大的異步功能支持
之前已經(jīng)簡單介紹過,請移步NodeJS異步流程控制簡單介紹。為什么要將這個中間件呢,因為當你接觸nodejs代碼多了之后,難免會受到“回調(diào)之痛”。各種的回調(diào)嵌套真的把你給玩壞了。代碼看起來就好像多層的if-else嵌套一樣。
比如我們做用戶注冊功能,保存用戶之前,要先判斷一下用戶名是否已經(jīng)存在,大致代碼如下:
var user = req.body;
var User = dbHelper.User;
User.findOne({username: user.username}, function (err, doc) {
if(err){
next(err);
}else{
if(doc){ // 用戶名已被占用
req.flash('error', '用戶名已被占用');
res.redirect('/reg');
}else{
User.create(user, function (err, doc) {
if(err){
next(err);
}else{
req.flash('success', '注冊成功,請登錄!');
res.redirect('/login');
}
});
}
}
});
對于數(shù)據(jù)庫的操作我們嵌套了兩層。再進一步,加入保存成功后,自動為注冊用戶綁定一些數(shù)據(jù)并存到數(shù)據(jù)庫,同時在跳轉(zhuǎn)成功的頁面進行展示呢?是不是又要多嵌套兩層?這時候我們的代碼已經(jīng)面目全非了!
這時候改async出場了。
async將各種嵌套的異步進行有效組織,增加了代碼的可維護性(雖然是為 Node.js 設(shè)計的,但是它也可以直接在瀏覽器中使用)。
Async 提供了大約20個函數(shù),包括 map, reduce, filter, forEach 等等,也有常用的異步流程控制模式,并行,瀑布等等。官方文檔里有詳細的說明,并且有實例,這里我們介紹一下兩個最常用的:parallel
、waterfall
。
parallel
并行執(zhí)行多個函數(shù),每個函數(shù)都是立即執(zhí)行,不需要等待其它函數(shù)先執(zhí)行。傳給最終callback的數(shù)組中的數(shù)據(jù)按照tasks中聲明的順序,而不是執(zhí)行完成的順序。
async.parallel([
function(callback){
setTimeout(function(){
callback(null, 'one');
}, 200);
},
function(callback){
setTimeout(function(){
callback(null, 'two');
}, 100);
}
],
// optional callback
function(err, results){
// the results array will equal ['one','two'] even though
// the second function had a shorter timeout.
});
parallel中的函數(shù)是并行的,沒有先后之分,callback中results參數(shù)的結(jié)果跟并行函數(shù)順序有關(guān)。上例中results值為['one', 'two']。
在本程序中,用戶注冊時,我們要校驗用戶名和郵箱是否被占用。分析一下:校驗用戶名和校驗郵箱并有沒先后循序,可以并行校驗。我們只需要拿到校驗后的結(jié)果,做出處理即可。示例代碼如下:
async.parallel({
username: function (callback) {
User.findOne({username: user.username}, function (err, doc) {
callback(null, doc);
});
},
email: function (callback) {
User.findOne({email: user.email}, function (err, doc) {
callback(null, doc);
});
}
}, function (err, results) {
if (results.username) {
req.flash(config.constant.flash.error, '用戶名已被占用');
res.redirect('/join');
return;
}
if (results.email) {
req.flash(config.constant.flash.error, '郵箱已被占用');
res.redirect('/join');
return;
}
user.password = utils.md5(user.password, 'base64');
User.create(user, function (err, doc) {
webHelper.reshook(err, next, function () {
req.flash(config.constant.flash.success, '注冊成功,請登錄!');
res.redirect('/login');
});
});
});
waterfall
按順序依次執(zhí)行一組函數(shù)。每個函數(shù)產(chǎn)生的值,都將傳給下一個函數(shù)。
waterfall跟parallel相反,是順序執(zhí)行一組函數(shù)。
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});
第一個函數(shù)返回兩這值one、two,由于waterfall是順序執(zhí)行的,所有等第一個函數(shù)執(zhí)行完,才會繼續(xù)執(zhí)行第二個函數(shù),并且one、two傳遞給了第二個函數(shù),所以在第二個函數(shù)中arg1值為'one',arg2值為'two',然后通過callback,將three傳給了第三個函數(shù),所以第三個函數(shù)arg1值為'three',最后將'done'傳給了最后的回調(diào)函數(shù),所以result值為'done'。
那么在我們的程序中是怎么應用的呢?比如展示用戶詳情頁面中/u/username,我們需要展示用戶的基本信息,同時將此用戶的文章進行展示。前臺傳遞到后臺的參數(shù)是username,而我們只能通過userId才能查詢文章,所以我們需要先通過username查詢user,在通過user.id查詢此用戶的所有文章articles,然后將user和articles都傳到前臺,進行展示,代碼如下:
router.get('/:username', function (req, res, next) {
var username = req.params.username;
var User = dbHelper.User;
var Article = dbHelper.Article;
async.waterfall([
function (callback) {
User.findOne({username: username}).exec(function (err, user) {
callback(null, user);
});
},
function (user, callback) {
if (user) {
Article.find({_user: user.id}).populate('_user').exec(function (err, articles) {
callback(null, articles, user);
});
} else {
callback(null, null);
}
}
], function (err, articles, user) {
res.render('my', {
articles: articles,
user: user,
menu: 'my'
});
});
});
總結(jié):async官方示例中說的很詳細了,它的功能非常強大,需要我們一個個將其摸索透。最終組織出漂亮的代碼出來。
官方文檔:https://github.com/caolan/async#asyncjs
添加自定義的404頁面
expressjs生成的代碼app.js中,默認404是當作500錯誤進行處理的,當我們請求到404后,會給出這樣一個錯誤頁面

而實際上404跟500是不一樣的,500是服務器端程序錯誤,404是很常見的一種資源不存在的錯誤,500能避免,但是404是不可避免的,所以我們需要有好的提示給用戶一個404頁面。改善方法如下:
在app.js中找到catch 404 and forward to error handler對應的方法:
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
問題就出在next(err),將err傳遞給下一個方法,也就是500那個。這里我們阻斷它繼續(xù)傳遞,直接渲染到前臺頁面:
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
res.render('404');
});
然后在views下添加一個404.hbs,定制一下就ok!效果如下:

你可以自己訂制的更漂亮!
使用Handlebars模塊化你的頁面
已經(jīng)有一篇詳細的文章來單獨說明這個知識點,請移步:http://www.itdecent.cn/p/a38ec7ef339a
請關(guān)注專題:我的NodeJS學習之路(實踐之路)