1. 程式人生 > >Nodejs 回撥函式中的坑以及中介軟體的用法

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表示呼叫下一個中介軟體,只能在非結尾的中介軟體中使用