2013-08-23 77 views
5

我無法理解如何處理它看起來像是快遞的一個非常基本的方面。如果我有一些在異步回調中引發異常的代碼,我無法捕獲該異常,因爲在回調運行時try/catch塊不在範圍內。在這些情況下,瀏覽器將掛起,直到它最終放棄說明服務器沒有響應。這是一個非常糟糕的用戶體驗。我寧願能夠立即向客戶返回500錯誤。默認的快速錯誤處理程序顯然不處理這種情況。下面是一些示例代碼:快速處理異常

var express = require("express"); 

var app = express(); 
app.use(app.router); 
//express error handler (never called) 
app.use(function(err, req, res, next) { 
    console.log(err); 
    res.send(500); 
}); 

app.get("/test", function(req, res, next) { 
    require("fs").readFile("/some/file", function(err, data) { 
     a.b(); //blow up 
    }); 
}); 

app.listen(8888); 

在上面的代碼中,線A·B()拋出一個「的ReferenceError:一個沒有定義」異常。定義的錯誤處理程序從不被調用。請注意,在這種情況下,由fs.readFile()返回的err對象爲空,因爲文件已被正確讀取。該錯誤是異步處理程序中的代碼。

我已閱讀this post關於使用節點的uncaughtExpception,但the documentation說不使用該方法。即使我使用它,我又會如何將500響應發送回用戶?快速響應對象不再供我使用。

那麼你如何處理這種情況?

+0

爲什麼會在回調中拋出錯誤?更有可能的是,MongooseObject會在內部拋出一個錯誤,在那裏你可以捕獲它,然後'回調(錯誤)'。現在上面顯示的回調有一個虛假的錯誤對象,你可以發送500響應。 – Plato

+0

如果Mongoose進入不能回調的錯誤......嗯,我不知道你可以創建一個setTimeout包裝器。但我希望所有錯誤都能立即傳遞給回調函數 – Plato

+0

這不是我擔心的Mongoose,我只是用它來調用異步函數。我更擔心自己的代碼做了一些愚蠢的事情,導致拋出異常。我會更新示例代碼以使其更清晰。 – d512

回答

4

好吧,我只是要發佈一個完全不同的答案,因爲我的第一個答案有一些有價值的信息,但事後看來是脫離主題。

簡短回答:正確的事情是已經發生的事情:你的程序應該打印一個堆棧跟蹤並退出並報錯。

的基本思路

所以我認爲你需要考慮在不同類別的錯誤。我的第一個回答涉及數據相關的錯誤,一個寫得很好的程序可以並且應該乾淨地處理。你所描述的是一個CRASH。如果您閱讀了鏈接到的node.js文檔,則這是正確的。您的程序在這一點上唯一有用的功能是通過堆棧跟蹤退出,並允許進程管理員重新啓動並獲得理解狀態。一旦程序崩潰,由於極其廣泛的錯誤可能是異常進入堆棧頂部的根本原因,它本質上是不可恢復的。在您的具體示例中,每次都會繼續發生此錯誤,直到修復源代碼錯誤並重新部署應用程序。如果您擔心未經測試的錯誤代碼將進入您的應用程序,那麼添加更多未經測試的錯誤處理代碼並不能真正解決正確的問題。

但是總之,沒有辦法獲得引發這個異常的HTTP請求對象的引用,所以AFAIK你不能改變瀏覽器中最終用戶所感知的方式,除了在中間反向代理層,您可以在其中配置原始超時併發送更友好的錯誤頁面(對於任何不是完整的HTML文檔的請求,這當然是無用的)。

錯誤處理的節點

Error Handling in Node.js聖經由Dave帕切科這個主題在我看來權威著作。它是全面的,廣泛的,徹底的。我建議定期閱讀和重新閱讀。


爲了解決@ asparagino的意見,如果未處理的異常是容易複製或高頻發生的事情,它不是一個邊緣的情況下,這是一個錯誤。正確的做法是改善您的代碼,以防止在這種情況下生成未捕獲的異常。實際上處理條件,從而將程序員錯誤轉換爲操作錯誤,並且程序可以在沒有重新啓動且沒有未捕獲異常的情況下繼續。

+0

謝謝你的答案彼得。這是我從我讀過的東西中猜出來的。我不確定我是否同意100%的想法,即一個程序在意外的異常情況下可以做的唯一有用的事情是重啓。顯然,從最終用戶的角度來看,這不是一個好的體驗。除此之外,它會影響當時正在進行的所有其他請求。我寫了大量利用頂級異常處理程序返回500的Web應用程序,記錄錯誤,但保持服務正常運行。無論如何,它工作的方式,我只需要處理它。再次感謝。 – d512

+0

是的,我認爲這主要是基於事件的IO不可避免的後果。 JavaScript超級動態的特性也使得錯誤更容易投入生產。也許其他答案會提出一些很好的建議。 –

+3

@PeterLyons - 節點進程通常會處理數十甚至數千個併發請求。其中每一個都可以包含有價值的數據,如果它沒有完成,將會有成本。你正在建造一個玩雜耍的人。它應該一滴下所有的球嗎? 應記錄錯誤和異常。應該檢查日誌,並跟蹤並修復錯誤和邊緣條件。這個過程不應該放棄。 – asparagino

4

您應該通過app.use(error, req, res, next)使用express的錯誤處理中間件。 Express會維護一個單獨的中間件堆棧,當正常的中間件堆棧拋出未捕獲的異常時,它會使用它。 (注意,表達式只是查看回調的參數(期望參數的數量),將其歸類爲常規中間件或錯誤處理中間件,這有點神奇,所以請記住,您必須聲明上述參數以便快速理解這是一個錯誤處理中間件)。

根據你的問題和意見,只要明白在node.js中異常並不是那麼有用,因爲每個異步調用都會得到一個新的堆棧,這就是爲什麼回調在任何地方都被使用,並且第一個參數是普遍錯誤的原因。在示例中,您的try/catch塊只會捕獲由findById直接拋出的異常(就像id未定義,因爲它在您的代碼段中),但一旦實際調用數據庫,就是這樣,調用堆棧是直到在節點調用異步IO回調時啓動完全不同的調用堆棧時纔會發生進一步的異常。

Thanks for the answer, but this only works if I put the try/catch inside the async callback and have the catch do next(exp). I'd like to avoid having separate try/catch blocks in every single async callback.

不,這是不正確的。您不必手動撥打next(exp)。 Express會捕獲錯誤併爲您觸發錯誤處理中間件(無論如何,它都是開發人員友好的異常報告頁面)。即使在「正常」錯誤條件下,異步庫也不會拋出異常。他們將錯誤傳遞給回調函數,所以一般情況下,您不必爲節點中的try/catch而煩惱。永遠不要忽略傳遞給回調函數的錯誤參數,並且你沒事。

您沒有看到這個樣板在節點:

someDb.query(someCriteria, function (error, result) { 
    try { 
    //some code to deal with result 
    } catch (exception) { 
    callback(exception); 
    } 
}); 

你看到這雖然:

someDb.query(someCriteria, function (error, result) { 
    if (error) { 
    callback(error); 
    return; 
    } 
    //some code to deal with result 
}); 

節點處理IO不同,這意味着調用堆棧的工作方式不同,這意味着異常工作方式不同,這意味着錯誤處理工作方式不同您可以編寫一個穩定的節點/快速應用程序,在不編寫任何try/catch的情況下處理錯誤而不會崩潰。 (express有一個處理未被捕獲的錯誤,一直到最頂層)。這不是一個功能限制,它只是異步IO的一個順序,意味着你必須以不同的方式編寫代碼,並用回調而不是異常來處理錯誤。把它看作是一種「限制」,而不是「它的方式」,是對真正只是技術現實的東西提出了否定的含義。在同步和異步範例中都有乾淨而強大的異常處理模式。

+0

感謝您的回答,但這隻適用於將try/catch放在異步回調中並讓catch執行next(exp)的情況。我想避免在每個異步回調中分開try/catch塊。 – d512

+0

是的,我想我理解這個問題,我只是不知道該怎麼辦。在某些時候,代碼會拋出異常,我不希望節點進程崩潰或掛起。這只是節點的固有限制嗎? – d512

+0

彼得,感謝您花時間發佈完整的答案。要麼我沒有很好地解釋我的問題,要麼我在錯誤處理代碼中做錯了什麼。你說「Express會發現錯誤併爲你觸發錯誤處理中間件」,但我似乎無法完成這項工作。我修改了我的問題,以包含一個簡單但完整的示例,演示了我所看到的內容。 – d512