Nodejs 回撥函式中的坑以及中介軟體的用法
在用Nodejs + express 開發後臺的過程中,最令人頭疼的就是到處存在的回撥函數了。不管是http請求,還是資料庫請求,都是強制回撥的。這是由js本身的特性導致的。
所謂回撥,就是指假設A將任務分配給B去執行。之後A就可以把這個任務放在一邊,去執行其他任務。當B執行完以後,將結果告訴A,A會撿起之前沒完成的任務繼續做。有點類似於中斷的模式。這樣一來,雖然程式的效能得以保證,但是許多問題也是隨之而來。
第一個問題是異常捕獲變得更加困難。如果在A所執行程式的兩端加上try…catch,而在B執行回撥過程中發生了錯誤,A是不會報錯的。這點要特別注意。所以要特別注意對返回函式中異常資訊的處理。這點是比較令人困擾的。舉例如下
典型的回撥函式寫法
try{
// 程式A
req.models.vote.create(record, funtion(err, result){
// 回撥函式
if (err) {
// blablabla...
// 對B執行結果的異常處理
}
// A需要完成的接下來的任務
})
}catch(err){
// A的異常處理
}
上面這段程式用來在mydql中建立新資料行。當然其他回撥的用法也差不多。可以看到,雖然A有try catch對,但是在回撥過程中,如果B發生了錯誤,那麼A是不會報錯的。只是在回撥函式funtion(err, result)中,有相關的錯誤資訊。
第二個問題是回撥函式使得程式碼的執行順序不再線性,有可能會造成邏輯混亂。還是拿上面的程式舉例,稍微修改下
try{
// A的第一塊程式碼
req.models.vote.create(record, funtion(err, result){
// 回撥函式
if (err) {
// blablabla...
// 對B執行結果的異常處理
}
// A需要完成的接下來的任務
})
// A的第二塊程式碼
// blablabla...
// A的第三塊程式碼
// blablabla...
}catch(err){
// A的異常處理
}
上面這段程式,A在執行第一塊程式碼時,把一個任務交給B去處理。關鍵是,之後A會按順序執行下面的程式碼,第二塊程式碼,第三塊程式碼… 以此類推。那麼問題來了,回撥函式有可能先於程式碼塊2被執行,也有可能晚於程式碼塊2.那麼如果程式碼塊2和回撥函式呼叫了同一個資源,就很有可能會報錯。我在寫的時候就碰到過一個,使得對一個http請求重複返回了2次內容,但是一個http只能有一個返回呀,於是就報錯了。
解決的辦法,就是將A後續的程式碼都放進回撥函式中。或者對於兩個並行的邏輯,儘量使用if-else邏輯,而不要用if-return邏輯。如下所示
// ---------------------------------------------------
// 【將A後續程式碼放入回撥函式中】
req.models.vote.create(record, funtion(err, result){
// 回撥函式
if (err) {
// blablabla...
// 對B執行結果的異常處理
}
// A的第二塊程式碼
// A的第三塊程式碼
// ..
})
// ---------------------------------------------------
// 【儘量使用if-else邏輯】
// 這樣就只能執行A,B中的一個,而不會同時執行,導致報錯
if ( logic 1 ){
// 回撥函式A
return
}
else {
// 回撥函式B
return
}
// ---------------------------------------------------
// 【不要使用下面這種方法】
// 看似能夠精簡程式碼,實則有可能會導致A和B都執行
if ( logic 1 ){
// 回撥函式 A
return
}
//回撥函式 B
return
第三個問題,頻繁使用回撥會使得程式碼邏輯一片混亂,巢狀過深,可讀性和可維護性都非常糟糕。被稱作”callback hell”。比如有一個業務邏輯,需要讀取一次網頁,然後讀取若干次資料庫。。。於是你的函式就變成這樣了。。。
req.models.vote.create(record, funtion(err, result){
// 回撥函式B
if (err) {
// blablabla...
// 對B執行結果的異常處理
}
req.models.xxx.find(..,function(err,result){
// 回撥函式C
if (err){
// 對C執行結果的異常處理
}
req.models.xxx.find(..,function(err,result){
// 回撥函式D
if (err) {..}
})
})
})
如上所示,不停的巢狀巢狀巢狀。。。。畫面太美不敢看。。這時候為了讓程式碼的邏輯更加清晰,需要使用中介軟體。在express中,是可以使用中介軟體來處理業務邏輯的。(其他的我不太清楚,應該也是有的)。所謂的中介軟體有點類似於linux中的管道,前一個函式執行完以後,如果沒有返回,就由下一個函式處理。如下所示。(以express應用為例)
// 【router.js】
var test = require('./test.js')
app.post('/',test.a,test.b) ;
// 【test.js】
module.exports.a = function(req,res,next){ //必須有這三個變數
req.models.vote.create(record, funtion(err, result){
// 回撥函式B
if (err) {
// blablabla...
// 對B執行結果的異常處理
return res ; //返回http請求,不進入下一單元
}
// 一些非非同步的操作..
next() ;
// 如果後面還有中介軟體,則next表示傳給下箇中間件
}
} ;
module.exports.b = function(req,res,next){
req.models.vote.create(record, funtion(err, result){
// 回撥函式B
if (err) {
// blablabla...
// 對B執行結果的異常處理
return res ;
}
// 一些非非同步的操作..
return res ;
// 如果是最後一箇中間件,則不能有next函式,不然會導致無響應
}
} ;
每個中介軟體的引數都是req,res,next. req中有一些請求的必要資訊,以及有可能有上一個中介軟體的執行結果,res表示要返回給客戶端的內容,next表示呼叫下一個中介軟體,只能在非結尾的中介軟體中使用