node是對錯誤處理要求比較高的語言,假如對錯誤處理沒有到位可能會造成程式程式退出
01 前言
錯誤處理是程式中一個重要的部分,也是判斷你的程式是否專業的標準。一般來說我們寫程式的時候都會選擇使用try...catch來進行錯誤捕獲,或者有時候我們會使用throw進行錯誤丟擲,這是都是常用的錯誤捕獲方法。但是我們在進行node進行開發的時候就會接觸到非同步過程的中的錯誤處理。
我們知道在node開發的時候會運用到很多第三方的模組,比如我們經常會使用最大的包管理工具npm,裡面下載的包都會放到我們專案當中的node_modules裡面,我們開啟可以看到裡面包含的檔案很多,程式碼量也是巨大的。這裡面就會有很多的bug隱患在裡面,這時候使用錯誤捕獲就非常有用了。
其實我們一開始想到的就是在全域性範圍內進行錯誤的監聽,node提供了一個uncaughtException捕獲異常,但是這種方法我們會難以定位到錯誤的發生位置。不應該把該函式當成萬能的捕獲模組,而是最後的解決方案。
02 Error模組
Error定義了Node中常見的錯誤型別,我們可以使用Error進行錯誤的丟擲。Error模組裡面包含了一個堆疊軌跡用於描述Error是從哪裡產生的,一般來說我們可以準確知道錯誤發生在哪一部分的程式碼當中,根據錯誤的描述資訊可以快速定位到錯誤。
var fs = require("fs");
fs.readFile("file",function(err,data){
if(err){
throw new Error("Error!")
}
})
複製程式碼
Node程式中產生的所有Error都是使用Error類的例項或者繼承自Error類。我們在程式程式碼不中不僅可以使用回撥函式自帶的Error模組,而且我們可以顯示第捕獲錯誤。比如當你知道邏輯程式碼執行都某一部分是不對的,應該進行錯誤的捕獲和提醒,你就可以使用:
throw new Error("自定義錯誤資訊!")
複製程式碼
03 錯誤捕獲方式
接下來就簡單介紹一下Node中我們是如何進行錯誤捕獲的,總的來說我們可以有以下三種方式,try/catch、callback、event。之前我們常用的try/catch方式只適用於同步的呼叫情況,但是我們知道node中會出現很多的非同步呼叫方式。
try/catch
首先我們應該瞭解的是在非同步操作當中該方法是無法捕獲錯誤的,主要原因就是因為非同步呼叫返回時,程式碼的上下文已經改變,回撥函式當中的程式碼已經脫離了try/catch的範圍,所以是無法捕獲的。
同步呼叫情況:
//這裡可以捕獲
try{
throw new Error("這裡出錯了!");
}catch(e){
console.log(e)
}
複製程式碼
非同步呼叫情況:
try{
setTimeout(function(){throw new Error('這裡出錯了!')},1000)
}catch(e){
console.log(e);//這裡無法進行捕獲
}
複製程式碼
callback
回撥函式的方式主要是通過引數的判斷來確定的,node中通常回撥函式都會接受兩個引數error和result。這兩個值肯定會有一個不為空,我們通過讀取本地檔案的操作來舉一個例子。(因為方法返回的是buffer物件難以閱讀,我們就是使用utf8進行讀取,最後字串轉成json)
var fs = require("fs");
fs.readFile("./a.json",'utf8', function(error, result) {
if (error) {
console.log(error);
return;
}
console.log(JSON.parse(result));
});
複製程式碼
假如檔案存在就會返回輸出結果,故意寫成a1.json不存在就會丟擲錯誤:
{ [Error: ENOENT: no such file or directory, open 'D:\test\a1.json']
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'D:\\test\\a1.json' }
複製程式碼
Event錯誤處理
我們進行對檔案流監聽的時候,即使檔案流讀取是一個同步的方法,但是我們依舊不能使用try/catch來捕獲,為什麼呢?因為該方法返回了一個物件,只能使用事件處理的方式來處理異常。如果使用try/catch的話直接報錯退出,使用事件監聽的方式就不會影響程式的執行且會報出錯誤資訊。
所以正確的方式應該是這樣的:
var fs = require("fs");
var stream = fs.createReadStream('./a.json');
stream.on("error",function(err){
console.log(err)
})
複製程式碼
04 Domain模組
domain模組檢視在一個更高的維度上面解決以上提到的三種錯誤(可處理callback與event形式),但是現在這個模組已經是不推薦使用了。首先它的出發點就是把不同的處理方式統一到這個模組裡面監聽和捕獲。
它的用法是使用了create方法進行建立Domain物件,然後通過Domain物件監聽某物件的error事件,且定義好了相應的處理邏輯,最後使用run方法來啟動整個Domain,run方法裡面的內容就是我們準備監聽的程式碼。
//處理callback
var fs = require("fs");
var domain = require("domain");
var d = domain.create();
d.run(function(){
fs.readFile('./a1.json','utf8',function(err,data){
if(err){
throw new Error('error')
}
console.log(data)
})
})
d.on('error',function(err){
console.log(err)
})
複製程式碼
//處理event
var fs = require("fs");
var domain = require("domain");
var d = domain.create();
d.run(function(){
fs.createReadStream('./a1.json')
})
d.on('error',function(err){
console.log(err)
})
複製程式碼
除此之外,domain可以支援手動呼叫add方法把物件新增到監聽列表當中。由此可見,Domain其實就是將需要管理的物件包裹起來然後通過run與add方法進行處理和實現。但是如果我們想要把整一個web服務監聽的話就是把所有程式碼都放到run方法裡面,可能會造成記憶體洩露,而且手動呼叫add方法很難以接受,假如物件被遺漏就可能會花費很多時間進行錯誤排查。
它的原理其實很簡單:
- 通過add方法將物件新增到其監聽列表當中(有一個menber屬性,維護監聽的物件)
- 監聽process.uncaughtException捕獲,假如程式碼被domain包裹就會觸發domain的error事件
- 通過將非同步的實現方式,加入domain進行監聽,這樣就可以把所有的不同的錯誤處理機制統一到一個物件
05 ES6中的錯誤處理
ES6我們在工作中用的比較多,比如我們常用的就有promise物件了,還有async/await的形式,被稱為是非同步的終極解決方案。所以我們也來談一下ES6中如何進行錯誤處理。
Promise
首先第一個肯定是promise了,因為這對於回撥函式的操作很友好,避免了一些回撥地獄的產生,也提供了try/catch的形式捕獲異常。
var promise = new Promise((resolve,reject){
throw new Error("出錯啦!")
})
promise.catch(function(error){
console.log(error);
})
複製程式碼
Generator與async
可以使用try/catch語句進行錯誤捕獲,當yield後面的非同步操作發生了錯誤,一樣可以使用try語句進行捕獲。
function * generator(){
try{
yield asyncFunction();
}catch(e){
console,log(e)
}
return 'end'
}
複製程式碼
假如我們使用async的形式來寫(其實就是語法糖,本質一樣),也可以使用try/catch來捕獲。假如await內部操作出錯則後續程式碼不會執行,可使用try進行包裹。
async function test(){
try{
await asyncFunction()
}catch(e){
console.log(e)
}
}
複製程式碼
06 小結
以上我們介紹瞭如何在非同步的世界裡面進行錯誤的捕獲,之前我們進行的程式碼編寫都是在同步的世界裡,使用try/catch就可以解決大部分你的問題。但是近年來我們出現了Node,同步的世界被打破了,所以我們也有必要學習一下如何進行錯誤的捕獲。
上面我們說了使用原始的方法try/catch、callback回撥函式、事件觸發機制三種方法。
假如我們遇到一些不可避免的錯誤,導致系統崩潰或者程式得不到正常執行,我們還是有最後的解決方法並且大部分是有效的,那就是:重啟試試!