`io.js`生成的錯誤分為兩類:`JavaScript`錯誤和系統錯誤。所有的錯誤都繼承于`JavaScript`的`Error`類,或就是它的實例。并且都至少提供這個類中可用的屬性。
當一個操作因為語法錯誤或語言運行時級別(language-runtime-level)的原因不被允許時,一個`JavaScript error`會被生成并拋出一個異常。如果一個操作因為系統級別(system-level)限制而不被允許時,一個系統錯誤會被生成。客戶端代碼接著會根據API傳播它的方式來被給予捕獲這個錯誤的機會。
API被調用的風格決定了生成的錯誤如何回送(handed back),傳播給客戶端。這反過來告訴客戶端如何捕獲它們。異常可以通過`try / catch`結構捕獲;其他的捕獲方式請參閱下文。
### JavaScript錯誤
`JavaScript`錯誤表示API被錯誤的使用了,或者正在寫的程序有問題。
#### Class: Error
一個普通的錯誤對象。和其他的錯誤對象不同,`Error`實例不指示任何 為什么錯誤發生 的原因。`Error`在它們被實例化時,會記錄下“堆棧追蹤”信息,并且可以會提供一個錯誤描述。
注意:`io.js`會將系統錯誤以及`JavaScript`錯誤都封裝為這個類的實例。
#### new Error(message)
實例化一個新的`Error`對象,并且用提供的`message`設置它的`.message`屬性。它的`.stack`屬性將會描述`new Error`被調用時程序的這一刻。堆棧追蹤信息隸屬于V8堆棧追蹤API。堆棧追蹤信息只延伸到同步代碼執行的開始,或`Error.stackTraceLimit`給出的幀數(number of frames),這取決于哪個更小。
#### error.message
一個在`Error()`實例化時被傳遞的字符串。這個信息會出現在堆棧追蹤信息的第一行。改變這個值將不會改變堆棧追蹤信息的第一行。
#### error.stack
這個屬性返回一個代表錯誤被實例化時程序運行的那個點的字符串。
一個堆棧追蹤信息例子:
~~~
Error: Things keep happening!
at /home/gbusey/file.js:525:2
at Frobnicator.refrobulate (/home/gbusey/business-logic.js:424:21)
at Actor. (/home/gbusey/actors.js:400:8)
at increaseSynergy (/home/gbusey/actors.js:701:6)
~~~
第一行被格式化為`<錯誤類名>: <錯誤信息>`,然后是一系列的堆棧信息幀(以`“at”`開頭)。每幀都描述了一個最終導致錯誤生成的一次調用的地點。V8會試圖去給出每個函數的名字(通過變量名,函數名或對象方法名),但是也有可能它找不到一個合適的名字。如果V8不能為函數定義一個名字,那么那一幀里只會展示出位置信息。否則,被定義的函數名會顯示在位置信息之前。
幀只會由`JavaScript`函數生成。例如,如果在一個`JavaScript`函數里,同步執行了一個叫`cheetahify`的C++ `addon`函數,那么堆棧追蹤信息中的幀里將不會有`cheetahify`調用:
~~~
var cheetahify = require('./native-binding.node');
function makeFaster() {
// cheetahify *synchronously* calls speedy.
cheetahify(function speedy() {
throw new Error('oh no!');
});
}
makeFaster(); // will throw:
// /home/gbusey/file.js:6
// throw new Error('oh no!');
// ^
// Error: oh no!
// at speedy (/home/gbusey/file.js:6:11)
// at makeFaster (/home/gbusey/file.js:5:3)
// at Object.<anonymous> (/home/gbusey/file.js:10:1)
// at Module._compile (module.js:456:26)
// at Object.Module._extensions..js (module.js:474:10)
// at Module.load (module.js:356:32)
// at Function.Module._load (module.js:312:12)
// at Function.Module.runMain (module.js:497:10)
// at startup (node.js:119:16)
// at node.js:906:3
~~~
位置信息將會是以下之一:
- `native`,如果幀代表了向V8內部的一次調用(如在`[].forEach`中)。
- `plain-filename.js:line:column`,如果幀代表了向`io.js`內部的一次調用。
- `/absolute/path/to/file.js:line:column`,如果幀代表了向用戶程序或其依賴的一次調用。
關鍵的一點是,代表了堆棧信息的字符串只在需要被使用時生成,它是惰性生成的。
堆棧信息的幀數由 `Error.stackTraceLimit` 或 當前事件循環的`tick`里可用的幀數 中小的一方決定。
系統級別錯誤被作為增強的`Error`實例生成,參閱下文。
#### Error.captureStackTrace(targetObject[, constructorOpt])
為`targetObject`創建一個`.stack`屬性,它代表了`Error.captureStackTrace`被調用時,在程序中的位置。
~~~
var myObject = {};
Error.captureStackTrace(myObject);
myObject.stack // similar to `new Error().stack`
~~~
追蹤信息的第一行,將是`targetObject.toString()`的結果,而不是一個帶有`ErrorType:`前綴的信息。
可選的`constructorOpt`接收一個函數。如果指定,所有`constructorOpt`以上的幀,包括`constructorOpt`,將會被生成的堆棧追蹤信息忽略。
這對于向最終用戶隱藏實現細節十分有用。一個普遍的使用這個參數的例子:
~~~
function MyError() {
Error.captureStackTrace(this, MyError);
}
// without passing MyError to captureStackTrace, the MyError
// frame would should up in the .stack property. by passing
// the constructor, we omit that frame and all frames above it.
new MyError().stack
~~~
#### Error.stackTraceLimit
一個決定了堆棧追蹤信息的堆棧幀數的屬性(不論是由`new Error().stack`或由`Error.captureStackTrace(obj)`生成)。
初始值是`10`。可以被設置為任何有效的`JavaScript`數字,當值被改變后,就會影響所有的堆棧追蹤信息的獲取。如果設置為一個非數字值,堆棧追蹤將不會獲取任何一幀,并且會在要使用時報告`undefined`。
#### Class: RangeError
一個`Error`子類,表明了為一個函數提供的參數沒有在可接受的值的范圍之內;不論是在一個數字范圍之外,或是在一個參數指定的參數集合范圍之外。例子:
~~~
require('net').connect(-1); // throws RangeError, port should be > 0 && < 65536
~~~
`io.js`會立刻生成并拋出一個`RangeError`實例 -- 它們是參數驗證的一種形式。
#### Class: TypeError
一個`Error`子類,表明了提供的參數不是被允許的類型。例如,為一個期望收到字符串參數的函數,傳入一個函數作為參數,將導致一個類型錯誤。
~~~
require('url').parse(function() { }); // throws TypeError, since it expected a string
~~~
`io.js`會立刻生成并拋出一個`TypeError`實例 -- 它們是參數驗證的一種形式。
#### Class: ReferenceError
一個`Error`子類,表明了試圖去獲取一個未定義的對象的屬性。大多數情況下它表明了一個輸入錯誤,或者一個不完整的程序。客戶端代碼可能會生成和傳播這些錯誤,但實際上只有V8會。
~~~
doesNotExist; // throws ReferenceError, doesNotExist is not a variable in this program.
~~~
`ReferenceError`實例將有一個`.arguments`屬性,它是一個包含了一個元素的數組。這個元素表示沒有被定義的那個變量。
~~~
try {
doesNotExist;
} catch(err) {
err.arguments[0] === 'doesNotExist';
}
~~~
除非用戶程序是動態生成并執行的,否則,`ReferenceErrors`應該永遠被認為是程序或其依賴模塊的bug。
#### Class: SyntaxError
一個`Error`子類,表明了程序代碼不是合法的`JavaScript`。這些錯誤可能只會作為代碼運行的結果生成。代碼運行可能是`eval`,`Function`,`require`或`vm`的結果。這些錯誤經常表明了一個不完整的程序。
~~~
try {
require("vm").runInThisContext("binary ! isNotOk");
} catch(err) {
// err will be a SyntaxError
}
~~~
`SyntaxError`對于創建它們的上下文來說是不可恢復的 - 它們僅可能被其他上下文捕獲。
#### 異常 vs. 錯誤
一個`JavaScript`“異常”是一個無效操作或`throw`聲明所拋出的結果的值。但是這些值不被要求必須繼承于`Error`。所有的由`io.js`或`JavaScript`運行時拋出的異常都必須是`Error`實例。
一些異常在`JavaScript`層是無法恢復的。這些異常通常使一個進程掛掉。它們通常無法通過`assert()`檢查,或C++層中的`abort()`調用。
### 系統錯誤
系統錯誤在程序運行時環境的響應中生成。理想情況下,它們代表了程序能夠處理的操作錯誤。它們在系統調用級別生成:一個詳盡的錯誤碼列表和它們意義可以通過運行`man 2 intro`或`man 3 errno`在大多數`Unices`中獲得;或在線獲得。
在`io.js`中,系統錯誤表現為一個增強的`Error`對象 -- 不是完全的子類,而是一個有額外成員的`error`實例。
#### Class: System Error
#### error.syscall
一個代表了失敗的系統調用的字符串。
#### error.errno
#### error.code
一個代表了錯誤碼的字符串,通常是大寫字母`E`,可在`man 2 intro`命令的結果中查閱。
#### 常見系統錯誤
這個列表不詳盡,但是列舉了許多在寫`io.js`的過程中普遍發生的系統錯誤。詳盡的列表可以在這里查閱:`http://man7.org/linux/man-pages/man3/errno.3.html`
#### EPERM: 操作不被允許
試圖去執行一個需要特權的操作。
#### ENOENT: 指定的文件或目錄不存在
通常由文件操作產生;指定的路徑不存在 -- 通過指定的路徑不能找到實例(文件或目錄)。
#### EACCES: 沒有權限
試圖以禁止的方式去訪問一個需要權限的文件。
#### EEXIST: 文件已存在
執行一個要求目標不存在的操作時,一個已存在文件已經是目標。
#### ENOTDIR: 非目錄
給定的路徑存在,但不是期望的目錄。通常由`fs.readdir`產生。
#### EISDIR: 是目錄
一個操作期望接收一個文件,但給定的路徑是一個文件。
#### EMFILE: 系統中打開太多文件
達到了系統中允許的文件描述符的最大數量,那么下一個描述符請求,在已存在的最后一個描述符關閉之前,都不能被滿足。
通常在并行打開太多文件時觸發,特別是在那些將進程可用的文件描述符數量限制得很低的操作系統中(尤其是OS X)。為了改善這個限制,在同一個SHELL中運行`ulimit -n 2048`命令,再運行`io.js`進程。
#### EPIPE: 損壞的管道
向沒有讀取數據進程的管道,socket或FIFO中執行一個寫操作。通常在網絡和http層發生,表明需要被寫入的遠程流已經被關閉。
#### EADDRINUSE: 地址已被使用
試圖給一個服務器(net,http或https)綁定一個本地地址失敗,因為另一個本地系統中的服務器已經使用了那個地址。
#### ECONNRESET: 連接兩方重置(Connection reset by peer)
連接的雙方被強行關閉。通常是遠程`socket`超時或重啟的結果。通常由`http`和`net`模塊產生。
#### ECONNREFUSED: 拒絕連接
由于目標機器積極拒絕,沒有連接可以建立。通常是試圖訪問一個不活躍的遠程主機的服務的結果。
#### ENOTEMPTY: 目錄不為空
操作的實例要求是一個空目錄,但目錄不為空 -- 通常由`fs.unlink`產生。
#### ETIMEDOUT: 操作超時
因為被連接方在一段指定內未響應,連接或發送請求失敗。通常由http或net產生 -- 經常是一個 被連接`socket`沒有合適地調用`.end()`方法 的標志。
### 錯誤的傳播和捕獲
所有的`io.js`API將無效的參數視作異常 -- 也就是說,如果傳遞了非法的參數,他們會立刻生成并拋出一個`error`作為異常,甚至是異步API也會。
同步API(像`fs.readFileSync`)將會拋出一個錯誤。拋出值的行為是將值包裝入一個異常。異常可以被使用`try { } catch(err) { }`結果捕獲。
異步API有兩種錯誤傳播機制;一種代表了單個操作(Node風格的回調函數),另一種代表了多個操作(錯誤事件)。
#### Node風格的回調函數
單個操作使用`Node風格的回調函數` -- 一個提供給API作為參數的函數。Node風格的回調函數至少有一個參數 -- `error` -- 它可以是`null`(如果沒有錯誤發生)或是`Error`實例。例子:
~~~
var fs = require('fs');
fs.readFile('/some/file/that/does-not-exist', function nodeStyleCallback(err, data) {
console.log(err) // Error: ENOENT
console.log(data) // undefined / null
});
fs.readFile('/some/file/that/does-exist', function(err, data) {
console.log(err) // null
console.log(data) // <Buffer: ba dd ca fe>
})
~~~
注意,`try { } catch(err) { }`不能捕獲異步API生成的錯誤。一個初學者的常見錯誤是嘗試在Node風格的回調函數中拋出錯誤:
~~~
// THIS WILL NOT WORK:
var fs = require('fs');
try {
fs.readFile('/some/file/that/does-not-exist', function(err, data) {
// mistaken assumption: throwing here...
if (err) {
throw err;
}
});
} catch(err) {
// ... will be caught here -- this is incorrect!
console.log(err); // Error: ENOENT
}
~~~
這將不會正常運行!在Node風格的回調函數執行時,外圍的代碼`try { } catch(err) { }`)已經退出了。在大多數情況,在Node風格的回調函數內部拋出錯誤會使進程掛掉。如果啟用了`domain`,它們可以捕獲了被拋出的錯誤;相似的,如果給`process.on('uncaughtException')`添加了監聽器,那么它也將會捕獲錯誤。
#### 錯誤事件
另一個提供錯誤的機制是`error`事件。這常被用在基于流或基于`event emitter`的API中,它們自身就代表了一系列的異步操作(每一個單一的操作都可能成功或失敗)。如果在錯誤的源頭沒有添加`error`事件的監聽器,那么`error`會被拋出。此時,進程會因為一個未處理的異常而掛掉,除非提供了合適的`domains`,或監聽了`process.on('uncaughtException')`。
~~~
var net = require('net');
var connection = net.connect('localhost');
// adding an "error" event handler to a stream:
connection.on('error', function(err) {
// if the connection is reset by the server, or if it can't
// connect at all, or on any sort of error encountered by
// the connection, the error will be sent here.
console.error(err);
});
connection.pipe(process.stdout);
~~~
“當沒有沒有監聽錯誤時會拋出錯誤”這個行為不僅限與`io.js`提供的API -- 用戶創建的基于流或`event emitters`的API也會如此。例子:
~~~
var events = require('events');
var ee = new events.EventEmitter;
setImmediate(function() {
// this will crash the process because no "error" event
// handler has been added.
ee.emit('error', new Error('This will crash'));
});
~~~
與Node風格的回調函數相同,這種方式產生的錯誤也不能被`try { } catch(err) { }`捕獲 -- 它們發生時,外圍的代碼已經退出了。