1. 程式人生 > >Node.js錯誤處理最佳實踐

Node.js錯誤處理最佳實踐

錯誤處理讓人痛苦,長久以來Node.js很容易通過而不處理大量錯誤。但是要想建立一個健壯的Node.js程式就必須正確的處理錯誤,而且這並不難學。如果你實在沒有耐心,那就直接繞過長篇大論跳到“總結”部分吧。【原文

這篇文章會回答NodeJS初學者的若干問題:

  • 我寫的函式裡什麼時候該丟擲異常,什麼時候該傳給callback, 什麼時候觸發EventEmitter等等。
  • 我的函式對引數該做出怎樣的假設?我應該檢查更加具體的約束麼?例如引數是否非空,是否大於零,是不是看起來像個IP地址,等等等。
  • 我該如何處理那些不符合預期的引數?我是應該丟擲一個異常,還是把錯誤傳遞給一個callback。
  • 我該怎麼在程式裡區分不同的異常(比如“請求錯誤”和“服務不可用”)?
  • 我怎麼才能提供足夠的資訊讓呼叫者知曉錯誤細節。
  • 我該怎麼處理未預料的出錯?我是應該用 try/catch ,domains 還是其它什麼方式呢?

這篇文章可以劃分成互相為基礎的幾個部分:

  • 背景:希望你所具備的知識。
  • 操作失敗和程式設計師的失誤:介紹兩種基本的異常。
  • 編寫新函式的實踐:關於怎麼讓函式產生有用報錯的基本原則。
  • 編寫新函式的具體推薦:編寫能產生有用報錯的、健壯的函式需要的一個檢查列表
  • 例子:以connect函式為例的文件和序言。
  • 總結:全文至此的觀點總結。
  • 附錄:Error物件屬性約定:用標準方式提供一個屬性列表,以提供更多資訊。

背景

本文假設:

  • 你已經熟悉了JavaScript、Java、 Python、 C++ 或者類似的語言中異常的概念,而且你知道丟擲異常和捕獲異常是什麼意思。
  • 你熟悉怎麼用NodeJS編寫程式碼。你使用非同步操作的時候會很自在,並能用callback(err,result)模式去完成非同步操作。你得知道下面的程式碼不能正確處理異常的原因是什麼[腳註1]
function myApiFunc(callback)
{
/*
 * This pattern does NOT work!
 */
try {
  doSomeAsynchronousOperation(function (err) {
    if (err)
      throw (err);
    /* continue as normal */
  });
} catch (ex) {
  callback
(ex); } }

你還要熟悉三種傳遞錯誤的方式: - 作為異常丟擲。 - 把錯誤傳給一個callback,這個函式正是為了處理異常和處理非同步操作返回結果的。 - 在EventEmitter上觸發一個Error事件。

接下來我們會詳細討論這幾種方式。這篇文章不假設你知道任何關於domains的知識。

最後,你應該知道在JavaScript裡,錯誤和異常是有區別的。錯誤是Error的一個例項。錯誤被建立並且直接傳遞給另一個函式或者被丟擲。如果一個錯誤被丟擲了那麼它就變成了一個異常[腳註2]。舉個例子:

throw new Error('something bad happened');

但是使用一個錯誤而不丟擲也是可以的

callback(new Error('something bad happened'));

這種用法更常見,因為在NodeJS裡,大部分的錯誤都是非同步的。實際上,try/catch唯一常用的是在JSON.parse和類似驗證使用者輸入的地方。接下來我們會看到,其實很少要捕獲一個非同步函式裡的異常。這一點和Java,C++,以及其它嚴重依賴異常的語言很不一樣。

操作失敗和程式設計師的失誤

把錯誤分成兩大類很有用[腳註3]:

  • 操作失敗 是正確編寫的程式在執行時產生的錯誤。它並不是程式的Bug,反而經常是其它問題:系統本身(記憶體不足或者開啟檔案數過多),系統配置(沒有到達遠端主機的路由),網路問題(埠掛起),遠端服務(500錯誤,連線失敗)。例子如下:
  • 連線不到伺服器
  • 無法解析主機名
  • 無效的使用者輸入
  • 請求超時
  • 伺服器返回500
  • 套接字被掛起
  • 系統記憶體不足

  • 程式設計師失誤 是程式裡的Bug。這些錯誤往往可以通過修改程式碼避免。它們永遠都沒法被有效的處理。

  • 讀取 undefined 的一個屬性

  • 呼叫非同步函式沒有指定回撥

  • 該傳物件的時候傳了一個字串

  • 該傳IP地址的時候傳了一個物件

人們把操作失敗和程式設計師的失誤都稱為“錯誤”,但其實它們很不一樣。操作失敗是所有正確的程式應該處理的錯誤情形,只要被妥善處理它們不一定會預示著Bug或是嚴重的問題。“檔案找不到”是一個操作失敗,但是它並不一定意味著哪裡出錯了。它可能只是代表著程式如果想用一個檔案得事先建立它。

與之相反,程式設計師失誤是徹徹底底的Bug。這些情形下你會犯錯:忘記驗證使用者輸入,敲錯了變數名,諸如此類。這樣的錯誤根本就沒法被處理,如果可以,那就意味著你用處理錯誤的程式碼代替了出錯的程式碼。

這樣的區分很重要:操作失敗是程式正常操作的一部分。而由程式設計師的失誤則是Bug。

有的時候,你會在一個Root問題裡同時遇到操作失敗和程式設計師的失誤。HTTP伺服器訪問了未定義的變數時奔潰了,這是程式設計師的失誤。當前連線著的客戶端會在程式崩潰的同時看到一個ECONNRESET錯誤,在NodeJS裡通常會被報成“Socket Hang-up”。對客戶端來說,這是一個不相關的操作失敗, 那是因為正確的客戶端必須處理伺服器宕機或者網路中斷的情況。

類似的,如果不處理好操作失敗, 這本身就是一個失誤。舉個例子,如果程式想要連線伺服器,但是得到一個ECONNREFUSED錯誤,而這個程式沒有監聽套接字上的 error事件,然後程式崩潰了,這是程式設計師的失誤。連線斷開是操作失敗(因為這是任何一個正確的程式在系統的網路或者其它模組出問題時都會經歷的),如果它不被正確處理,那它就是一個失誤。

理解操作失敗和程式設計師失誤的不同, 是搞清怎麼傳遞異常和處理異常的基礎。明白了這點再繼續往下讀。

處理操作失敗

就像效能和安全問題一樣,錯誤處理並不是可以憑空加到一個沒有任何錯誤處理的程式中的。你沒有辦法在一個集中的地方處理所有的異常,就像你不能在一個集中的地方解決所有的效能問題。你得考慮任何會導致失敗的程式碼(比如開啟檔案,連線伺服器,Fork子程序等)可能產生的結果。包括為什麼出錯,錯誤背後的原因。之後會提及,但是關鍵在於錯誤處理的粒度要細,因為哪裡出錯和為什麼出錯決定了影響大小和對策。

你可能會發現在棧的某幾層不斷地處理相同的錯誤。這是因為底層除了向上層傳遞錯誤,上層再向它的上層傳遞錯誤以外,底層沒有做任何有意義的事情。通常,只有頂層的呼叫者知道正確的應對是什麼,是重試操作,報告給使用者還是其它。但是那並不意味著,你應該把所有的錯誤全都丟給頂層的回撥函式。因為,頂層的回撥函式不知道發生錯誤的上下文,不知道哪些操作已經成功執行,哪些操作實際上失敗了。

我們來更具體一些。對於一個給定的錯誤,你可以做這些事情:

  • 直接處理。有的時候該做什麼很清楚。如果你在嘗試開啟日誌檔案的時候得到了一個ENOENT錯誤,很有可能你是第一次開啟這個檔案,你要做的就是首先建立它。更有意思的例子是,你維護著到伺服器(比如資料庫)的持久連線,然後遇到了一個“socket hang-up”的異常。這通常意味著要麼遠端要麼本地的網路失敗了。很多時候這種錯誤是暫時的,所以大部分情況下你得重新連線來解決問題。(這和接下來的重試不大一樣,因為在你得到這個錯誤的時候不一定有操作正在進行)

  • 把出錯擴散到客戶端。如果你不知道怎麼處理這個異常,最簡單的方式就是放棄你正在執行的操作,清理所有開始的,然後把錯誤傳遞給客戶端。(怎麼傳遞異常是另外一回事了,接下來會討論)。這種方式適合錯誤短時間內無法解決的情形。比如,使用者提交了不正確的JSON,你再解析一次是沒什麼幫助的。

  • 重試操作。對於那些來自網路和遠端服務的錯誤,有的時候重試操作就可以解決問題。比如,遠端服務返回了503(服務不可用錯誤),你可能會在幾秒種後重試。如果確定要重試,你應該清晰的用文件記錄下將會多次重試,重試多少次直到失敗,以及兩次重試的間隔。 另外,不要每次都假設需要重試。如果在棧中很深的地方(比如,被一個客戶端呼叫,而那個客戶端被另外一個由使用者操作的客戶端控制),這種情形下快速失敗讓客戶端去重試會更好。如果棧中的每一層都覺得需要重試,使用者最終會等待更長的時間,因為每一層都沒有意識到下層同時也在嘗試。

  • 直接崩潰。對於那些本不可能發生的錯誤,或者由程式設計師失誤導致的錯誤(比如無法連線到同一程式裡的本地套接字),可以記錄一個錯誤日誌然後直接崩潰。其它的比如記憶體不足這種錯誤,是JavaScript這樣的指令碼語言無法處理的,崩潰是十分合理的。(即便如此,在child_process.exec這樣的分離的操作裡,得到ENOMEM錯誤,或者那些你可以合理處理的錯誤時,你應該考慮這麼做)。在你無計可施需要讓管理員做修復的時候,你也可以直接崩潰。如果你用光了所有的檔案描述符或者沒有訪問配置檔案的許可權,這種情況下你什麼都做不了,只能等某個使用者登入系統把東西修好。

  • 記錄錯誤,其他什麼都不做。有的時候你什麼都做不了,沒有操作可以重試或者放棄,沒有任何理由崩潰掉應用程式。舉個例子吧,你用DNS跟蹤了一組遠端服務,結果有一個DNS失敗了。除了記錄一條日誌並且繼續使用剩下的服務以外,你什麼都做不了。但是,你至少得記錄點什麼(凡事都有例外。如果這種情況每秒發生幾千次,而你又沒法處理,那每次發生都記錄可能就不值得了,但是要週期性的記錄)。

(沒有辦法)處理程式設計師的失誤

對於程式設計師的失誤沒有什麼好做的。從定義上看,一段本該工作的程式碼壞掉了(比如變數名敲錯),你不能用更多的程式碼再去修復它。一旦你這樣做了,你就使用錯誤處理的程式碼代替了出錯的程式碼。

有些人贊成從程式設計師的失誤中恢復,也就是讓當前的操作失敗,但是繼續處理請求。這種做法不推薦。考慮這樣的情況:原始程式碼裡有一個失誤是沒考慮到某種特殊情況。你怎麼確定這個問題不會影響其他請求呢?如果其它的請求共享了某個狀態(伺服器,套接字,資料庫連線池等),有極大的可能其他請求會不正常。

典型的例子是REST伺服器(比如用Restify搭的),如果有一個請求處理函式丟擲了一個ReferenceError(比如,變數名打錯)。繼續執行下去很有肯能會導致嚴重的Bug,而且極其難發現。例如:

  1. 一些請求間共享的狀態可能會被變成nullundefined或者其它無效值,結果就是下一個請求也失敗了。
  2. 資料庫(或其它)連線可能會被洩露,降低了能夠並行處理的請求數量。最後只剩下幾個可用連線會很壞,將導致請求由並行變成序列被處理。
  3. 更糟的是, postgres 連線會被留在開啟的請求事務裡。這會導致 postgres “持有”表中某一行的舊值,因為它對這個事務可見。這個問題會存在好幾周,造成表無限制的增長,後續的請求全都被拖慢了,從幾毫秒到幾分鐘[腳註4]。雖然這個問題和 postgres 緊密相關,但是它很好的說明了程式設計師一個簡單的失誤會讓應用程式陷入一種非常可怕的狀態。
  4. 連線會停留在已認證的狀態,並且被後續的連線使用。結果就是在請求裡搞錯了使用者。
  5. 套接字會一直開啟著。一般情況下 NodeJS 會在一個空閒的套接字上應用兩分鐘的超時,但這個值可以覆蓋,這將會洩露一個檔案描述符。如果這種情況不斷髮生,程式會因為用光了所有的檔案描述符而強退。即使不覆蓋這個超時時間,客戶端會掛兩分鐘直到 “hang-up” 錯誤的發生。這兩分鐘的延遲會讓問題難於處理和除錯。
  6. 很多記憶體引用會被遺留。這會導致洩露,進而導致記憶體耗盡,GC需要的時間增加,最後效能急劇下降。這點非常難除錯,而且很需要技巧與導致造成洩露的失誤聯絡起來。

最好的從失誤恢復的方法是立刻崩潰。你應該用一個restarter 來啟動你的程式,在奔潰的時候自動重啟。如果restarter 準備就緒,崩潰是失誤來臨時最快的恢復可靠服務的方法。

奔潰應用程式唯一的負面影響是相連的客戶端臨時被擾亂,但是記住:

  • 從定義上看,這些錯誤屬於Bug。我們並不是在討論正常的系統或是網路錯誤,而是程式裡實際存在的Bug。它們應該在線上很罕見,並且是除錯和修復的最高優先順序。
  • 上面討論的種種情形裡,請求沒有必要一定得成功完成。請求可能成功完成,可能讓伺服器再次崩潰,可能以某種明顯的方式不正確的完成,或者以一種很難除錯的方式錯誤的結束了。
  • 在一個完備的分散式系統裡,客戶端必須能夠通過重連和重試來處理服務端的錯誤。不管 NodeJS 應用程式是否被允許崩潰,網路和系統的失敗已經是一個事實了。
  • 如果你的線上程式碼如此頻繁地崩潰讓連線斷開變成了問題,那麼正真的問題是你的伺服器Bug太多了,而不是因為你選擇出錯就崩潰。

如果出現伺服器經常崩潰導致客戶端頻繁掉線的問題,你應該把經歷集中在造成伺服器崩潰的Bug上,把它們變成可捕獲的異常,而不是在程式碼明顯有問題的情況下儘可能地避免崩潰。除錯這類問題最好的方法是,把 NodeJS 配置成出現未捕獲異常時把核心檔案打印出來。在 GNU/Linux 或者 基於 illumos 的系統上使用這些核心檔案,你不僅檢視應用崩潰時的堆疊記錄,還可以看到傳遞給函式的引數和其它的 JavaScript 物件,甚至是那些在閉包裡引用的變數。即使沒有配置 code dumps,你也可以用堆疊資訊和日誌來開始處理問題。

最後,記住程式設計師在伺服器端的失誤會造成客戶端的操作失敗,還有客戶端必須處理好伺服器端的奔潰和網路中斷。這不只是理論,而是實際發生在線上環境裡。

編寫函式的實踐

我們已經討論瞭如何處理異常,那麼當你在編寫新的函式的時候,怎麼才能向呼叫者傳遞錯誤呢?

最最重要的一點是為你的函式寫好文件,包括它接受的引數(附上型別和其它約束),返回值,可能發生的錯誤,以及這些錯誤意味著什麼。 如果你不知道會導致什麼錯誤或者不瞭解錯誤的含義,那你的應用程式正常工作就是一個巧合。 所以,當你編寫新的函式的時候,一定要告訴呼叫者可能發生哪些錯誤和錯誤的含義。

Throw, Callback 還是 EventEmitter

函式有三種基本的傳遞錯誤的模式。

  • throw以同步的方式傳遞異常--也就是在函式被呼叫處的相同的上下文。如果呼叫者(或者呼叫者的呼叫者)用了try/catch,則異常可以捕獲。如果所有的呼叫者都沒有用,那麼程式通常情況下會崩潰(異常也可能會被domains或者程序級的uncaughtException捕捉到,詳見下文)。

  • Callback 是最基礎的非同步傳遞事件的一種方式。使用者傳進來一個函式(callback),之後當某個非同步操作完成後呼叫這個 callback。通常 callback 會以callback(err,result)的形式被呼叫,這種情況下, err和 result必然有一個是非空的,取決於操作是成功還是失敗。

  • 更復雜的情形是,函式沒有用 Callback 而是返回一個 EventEmitter 物件,呼叫者需要監聽這個物件的 error事件。這種方式在兩種情況下很有用。

  • 當你在做一個可能會產生多個錯誤或多個結果的複雜操作的時候。比如,有一個請求一邊從資料庫取資料一邊把資料傳送回客戶端,而不是等待所有的結果一起到達。在這個例子裡,沒有用 callback,而是返回了一個 EventEmitter,每個結果會觸發一個row 事件,當所有結果傳送完畢後會觸發end事件,出現錯誤時會觸發一個error事件。

  • 用在那些具有複雜狀態機的物件上,這些物件往往伴隨著大量的非同步事件。例如,一個套接字是一個EventEmitter,它可能會觸發“connect“,”end“,”timeout“,”drain“,”close“事件。這樣,很自然地可以把”error“作為另外一種可以被觸發的事件。在這種情況下,清楚知道”error“還有其它事件何時被觸發很重要,同時被觸發的還有什麼事件(例如”close“),觸發的順序,還有套接字是否在結束的時候處於關閉狀態。

在大多數情況下,我們會把 callback 和 event emitter 歸到同一個“非同步錯誤傳遞”籃子裡。如果你有傳遞非同步錯誤的需要,你通常只要用其中的一種而不是同時使用。

那麼,什麼時候用throw,什麼時候用callback,什麼時候又用 EventEmitter 呢?這取決於兩件事:

  • 這是操作失敗還是程式設計師的失誤?
  • 這個函式本身是同步的還是非同步的。

直到目前,最常見的例子是在非同步函式裡發生了操作失敗。在大多數情況下,你需要寫一個以回撥函式作為引數的函式,然後你會把異常傳遞給這個回撥函式。這種方式工作的很好,並且被廣泛使用。例子可參照 NodeJS 的fs模組。如果你的場景比上面這個還複雜,那麼你可能就得換用 EventEmitter 了,不過你也還是在用非同步方式傳遞這個錯誤。

其次常見的一個例子是像JSON.parse這樣的函式同步產生了一個異常。對這些函式而言,如果遇到操作失敗(比如無效輸入),你得用同步的方式傳遞它。你可以丟擲(更加常見)或者返回它。

對於給定的函式,如果有一個非同步傳遞的異常,那麼所有的異常都應該被非同步傳遞。可能有這樣的情況,請求一到來你就知道它會失敗,並且知道不是因為程式設計師的失誤。可能的情形是你快取了返回給最近請求的錯誤。雖然你知道請求一定失敗,但是你還是應該用非同步的方式傳遞它。

通用的準則就是 你即可以同步傳遞錯誤(丟擲),也可以非同步傳遞錯誤(通過傳給一個回撥函式或者觸發EventEmitter的 error事件),但是不用同時使用。以這種方式,使用者處理異常的時候可以選擇用回撥函式還是用try/catch,但是不需要兩種都用。具體用哪一個取決於異常是怎麼傳遞的,這點得在文件裡說明清楚。

差點忘了程式設計師的失誤。回憶一下,它們其實是Bug。在函式開頭通過檢查引數的型別(或是其它約束)就可以被立即發現。一個退化的例子是,某人呼叫了一個非同步的函式,但是沒有傳回調函式。你應該立刻把這個錯丟擲,因為程式已經出錯而在這個點上最好的除錯的機會就是得到一個堆疊資訊,如果有核心資訊就更好了。

因為程式設計師的失誤永遠不應該被處理,上面提到的呼叫者只能用try/catch或者回調函式(或者 EventEmitter)其中一種處理異常的準則並沒有因為這條意見而改變。如果你想知道更多,請見上面的 (不要)處理程式設計師的失誤。

下表以 NodeJS 核心模組的常見函式為例,做了一個總結,大致按照每種問題出現的頻率來排列:

函式 型別 錯誤 錯誤型別 傳遞方式 呼叫者
fs.stat 非同步 file not found 操作失敗 callback handle
JSON.parse 同步 bad user input 操作失敗 throw try/catch
fs.stat 非同步 null for filename 失誤 throw none (crash)

非同步函式裡出現操作錯誤的例子(第一行)是最常見的。在同步函式裡發生操作失敗(第二行)比較少見,除非是驗證使用者輸入。程式設計師失誤(第三行)除非是在開發環境下,否則永遠都不應該出現。

吐槽:程式設計師失誤還是操作失敗?

你怎麼知道是程式設計師的失誤還是操作失敗呢?很簡單,你自己來定義並且記在文件裡,包括允許什麼型別的函式,怎樣打斷它的執行。如果你得到的異常不是文件裡能接受的,那就是一個程式設計師失誤。如果在文件裡寫明接受但是暫時處理不了的,那就是一個操作失敗。

你得用你的判斷力去決定你想做到多嚴格,但是我們會給你一定的意見。具體一些,想象有個函式叫做“connect”,它接受一個IP地址和一個回撥函式作為引數,這個回撥函式會在成功或者失敗的時候被呼叫。現在假設使用者傳進來一個明顯不是IP地址的引數,比如“bob”,這個時候你有幾種選擇:

  • 在文件裡寫清楚只接受有效的IPV4的地址,當用戶傳進來“bob”的時候丟擲一個異常。強烈推薦這種做法。
  • 在文件裡寫上接受任何string型別的引數。如果使用者傳的是“bob”,觸發一個非同步錯誤指明無法連線到“bob”這個IP地址。

這兩種方式和我們上面提到的關於操作失敗和程式設計師失誤的指導原則是一致的。你決定了這樣的輸入算是程式設計師的失誤還是操作失敗。通常,使用者輸入的校驗是很鬆的,為了證明這點,可以看Date.parse這個例子,它接受很多型別的輸入。但是對於大多數其它函式,我們強烈建議你偏向更嚴格而不是更鬆。你的程式越是猜測使用者的本意(使用隱式的轉換,無論是JavaScript語言本身這麼做還是有意為之),就越是容易猜錯。本意是想讓開發者在使用的時候不用更加具體,結果卻耗費了人家好幾個小時在Debug上。再說了,如果你覺得這是個好主意,你也可以在未來的版本里讓函式不那麼嚴格,但是如果你發現由於猜測使用者的意圖導致了很多惱人的bug,要修復它的時候想保持相容性就不大可能了。

所以如果一個值怎麼都不可能是有效的(本該是string卻得到一個undefined,本該是string型別的IP但明顯不是),你應該在文件裡寫明是這不允許的並且立刻丟擲一個異常。只要你在文件裡寫的清清楚楚,那這就是一個程式設計師的失誤而不是操作失敗。立即丟擲可以把Bug帶來的損失降到最小,並且儲存了開發者可以用來除錯這個問題的資訊(例如,呼叫堆疊,如果用核心檔案還可以得到引數和記憶體分佈)。

那麼 domains 和 process.on('uncaughtException') 呢?

操作失敗總是可以被顯示的機制所處理的:捕獲一個異常,在回撥裡處理錯誤,或者處理EventEmitter的“error”事件等等。Domains以及程序級別的‘uncaughtException’主要是用來從未料到的程式錯誤恢復的。由於上面我們所討論的原因,這兩種方式都不鼓勵。

編寫新函式的具體建議

我們已經談論了很多指導原則,現在讓我們具體一些。

  1. 你的函式做什麼得很清楚。

這點非常重要。每個介面函式的文件都要很清晰的說明: - 預期引數 - 引數的型別 - 引數的額外約束(例如,必須是有效的IP地址)

如果其中有一點不正確或者缺少,那就是一個程式設計師的失誤,你應該立刻丟擲來。

此外,你還要記錄:

  • 呼叫者可能會遇到的操作失敗(以及它們的name
  • 怎麼處理操作失敗(例如是丟擲,傳給回撥函式,還是被 EventEmitter 發出)
  • 返回值
  1. 使用 Error 物件或它的子類,並且實現 Error 的協議。

你的所有錯誤要麼使用 Error 類要麼使用它的子類。你應該提供namemessage屬性,stack也是(注意準確)。

  1. 在程式裡通過 Error 的 name 屬性區分不同的錯誤。

當你想要知道錯誤是何種型別的時候,用name屬性。 JavaScript內建的供你重用的名字包括“RangeError”(引數超出有效範圍)和“TypeError”(引數型別錯誤)。而HTTP異常,通常會用RFC指定的名字,比如“BadRequestError”或者“ServiceUnavailableError”。

不要想著給每個東西都取一個新的名字。如果你可以只用一個簡單的InvalidArgumentError,就不要分成 InvalidHostnameError,InvalidIpAddressError,InvalidDnsError等等,你要做的是通過增加屬性來說明那裡出了問題(下面會講到)。

  1. 用詳細的屬性來增強 Error 物件。

舉個例子,如果遇到無效引數,把 propertyName 設成引數的名字,把 propertyValue 設成傳進來的值。如果無法連到伺服器,用 remoteIp 屬性指明嘗試連線到的 IP。如果發生一個系統錯誤,在syscal 屬性裡設定是哪個系統呼叫,並把錯誤程式碼放到errno屬性裡。具體你可以檢視附錄,看有哪些樣例屬性可以用。

至少需要這些屬性:

name:用於在程式裡區分眾多的錯誤型別(例如引數非法和連線失敗)

message:一個供人類閱讀的錯誤訊息。對可能讀到這條訊息的人來說這應該已經足夠完整。如果你從更底層的地方傳遞了一個錯誤,你應該加上一些資訊來說明你在做什麼。怎麼包裝異常請往下看。

stack:一般來講不要隨意擾亂堆疊資訊。甚至不要增強它。V8引擎只有在這個屬性被讀取的時候才會真的去運算,以此大幅提高處理異常時候的效能。如果你讀完再去增強它,結果就會多付出代價,哪怕呼叫者並不需要堆疊資訊。

你還應該在錯誤資訊裡提供足夠的訊息,這樣呼叫者不用分析你的錯誤就可以新建自己的錯誤。它們可能會本地化這個錯誤資訊,也可能想要把大量的錯誤聚集到一起,再或者用不同的方式顯示錯誤資訊(比如在網頁上的一個表格裡,或者高亮顯示使用者錯誤輸入的欄位)。

  1. 若果你傳遞一個底層的錯誤給呼叫者,考慮先包裝一下。

經常會發現一個非同步函式funcA呼叫另外一個非同步函式funcB,如果

相關推薦

Node.js錯誤處理最佳實踐

錯誤處理讓人痛苦,長久以來Node.js很容易通過而不處理大量錯誤。但是要想建立一個健壯的Node.js程式就必須正確的處理錯誤,而且這並不難學。如果你實在沒有耐心,那就直接繞過長篇大論跳到“總結”部分吧。【原文】 這篇文章會回答NodeJS初學者的若干問題: 我寫

JS錯誤處理與除錯。

try{ window.someNonexistentFunction(); }catch(error){ console.log(error.message); } 錯誤物件的message屬性。 自己在做了一個例子。 try{ (function(){

Fundebug上線Node.js錯誤監控啦

作為全棧JavaScript錯誤實時監控平臺,Fundebug的Node.js實時錯誤監控服務上線啦,我們能夠幫助開發者及時,高效地發現並且解決Node.js錯誤,從而提高開發效率,並提升使用者體驗。 Fundebug為什麼監控Node.js? 程式設計師通常是比較自信的,他們

SpringBoot系列: Spring專案異常處理最佳實踐

===================================自定義異常類===================================稍具規模的專案, 一般都要自定義一組異常類, 這樣做的好處是:1. 可以充分利用異常的中斷特性, 簡化程式碼的邏輯控制. 2. 在自定義的異常類, 可以設定

Node.js錯誤 { [Function: checkError] [Symbol(Symbol.toPrimitive)]: [Function] }

剛開始學習nodejs,寫了一個引用nodejs內建的模組時出現{ [Function: checkError] [Symbol(Symbol.toPrimitive)]: [Function] }錯誤 出錯程式碼 const os = require('os'); console.

js錯誤處理權威指北

By Lukas Gisder-Dubé | nov 14, 2018 原文 接著我上一篇文章,我想談談異常。我肯定你之前也聽過——異常是個好東西。一開始,我們害怕異常,畢竟寫bug容易被人噴。其實通過修bug,我們實際上學會了下次開發怎麼避免這個bug並且可以做得更好。 在生活中,我們常說吃一塹長一智

.NET中的非同步程式設計——常見的錯誤最佳實踐

    在這篇文章中,我們將通過使用非同步程式設計的一些最常見的錯誤來給你們一些參考。 背景 在之前的文章中,我們開始分析.NET世界中的非同步程式設計。在那篇文章中,我們擔心這個概念有點誤解,儘管從.NET4.5開始它已經存在了超過6年時間。使用這種程式設計

Java小應用日誌級別異常處理最佳實踐

編者按:做了很多年IT工作,突然又對日誌級別有些迷茫,哈哈,為什麼要有又字。是的,人們都在實踐中不停的學習並增進自己。寫了好久的字,盯住看3分鐘,然後你會想,哦?寫錯了吧 ^_^ 日誌:我們應該做的更好了 我在說什麼?現在有大量的Java日誌框架和庫,大多數開發人員每天都

JS錯誤處理

主動觸發錯誤 throw  try  catch    debugger斷點與單步跟蹤 function getUserInput(msg){ var input=prompt(msg); var parsed=parseInt(input); if(parsed!

node.js 閃退監控實踐

前言: 在使用 node-webkit 搭建的桌面應用中,有時候應用會由於一些原因發生閃退,因此我們需要監控閃退的情況,給使用者一個提示,達到更為人性化的目的。近期便實現了這樣一個需求,過程中遇到一些困惑和問題,特此記錄。 實現過程描述: 如果node-webkit應用

Java 異常處理最佳實踐

在Finally語句塊中釋放資源或者使用Try-With-Resource語句 比如,在Try語句中使用InputStream輸入流,並且試圖在Try語句塊中關閉資源,這通常不是推薦做法。比如下面的程式碼就不是推薦做法。 public void doNotC

Node.js~ioredis處理耗時請求時連線數瀑增

回到目錄 關於redis連線數過高的解釋 對於node.js開發環境裡,使用傳統的redis或者使用ioredis都是不錯的選擇,而在處理大資料請求程中,偶爾出現了連線池( redis服務端的最大可用連線數,預設為1萬)不夠用的情況,一般的提示如下: It was not possible to c

Node.JS錯誤1:! js裡面的$報錯

<head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" type="text/css" href="/p

【win7下安裝node.js錯誤:roling back action】與【"grunt" 不是內部或外部命令】 解決方法

【win7下安裝node.js錯誤:roling back action】 解決方法: Node.js 是一個基於Chrome JavaScript 執行時建立的一個平臺, 用來方便地搭建快速的 易於擴充套件的網路應用· Node.js 藉助事件驅動, 非阻塞I/O 模型變得輕量和高效, 非常適合 執行在分

JSONP 在前端的傳送和後臺node.js處理

最近做一個模組,前端用的是vue,後臺用的是node.js,由於涉及到跨域,所以選擇用JSONP進行前後臺互動,講一下自己的心得體會: (1)後臺node.js 後臺node.js接收jsonp請求並返回資料非常簡單: var express = r

Node.js錯誤之關於formidable模組引用失敗

這裡,我使用webstorm建立的專案,專案檔案和nodejs的安裝檔案不在一個目錄,用webstorm執行新建的檔案時,控制檯輸出Error: Cannot find module 'formidable'

node.js下LDAP查詢實踐

目標: 從一個LDAP Server獲取uid=kxh的使用者資料 LDAP地址為:ldap://10.233.21.116:389 在工程根目錄中,先npm一個LDAP的訪問庫ldpajs npm install ldapjs在工程根目錄中,建立一個app.js

Atitit 攔截資料庫異常的處理最佳實踐

個人說明 提供相關技術諮詢,以及解決方案編制,編制相關標準化規範草案,軟體培訓與技術點體系建設,知識圖譜體系化,提供軟體行業顧問佈道,12年的軟體行業背景,歡迎有志於軟體行業的同仁們互相交流,群名稱:標準化規範工作組草案,群   號:518818717, 聯絡方式: [

JS——錯誤處理

//錯誤處理 閃退很不好 輸入-1,3345等 var n = 666.555; var d = prompt("請輸入小數位數"); try{ var result = n.toFixed(d); console.log(result);

Node.js非同步處理CPU密集型任務

Node.js非同步處理CPU密集型任務 Node.js擅長資料密集型實時(data-intensive real-time)互動的應用場景。然而資料密集型實時應用程式並不是只有I/O密集型任務,當碰到CPU密集型任務時,比如要對資料加解密(node.bcrypt.js)