io.js
生成的錯誤分為兩類:JavaScript
錯誤和系統(tǒng)錯誤。所有的錯誤都繼承于JavaScript
的Error
類,或就是它的實(shí)例。并且都至少提供這個類中可用的屬性。
當(dāng)一個操作因?yàn)檎Z法錯誤或語言運(yùn)行時級別(language-runtime-level)的原因不被允許時,一個JavaScript error
會被生成并拋出一個異常。如果一個操作因?yàn)橄到y(tǒng)級別(system-level)限制而不被允許時,一個系統(tǒng)錯誤會被生成??蛻舳舜a接著會根據(jù)API傳播它的方式來被給予捕獲這個錯誤的機(jī)會。
API被調(diào)用的風(fēng)格決定了生成的錯誤如何回送(handed back),傳播給客戶端。這反過來告訴客戶端如何捕獲它們。異??梢酝ㄟ^try / catch
結(jié)構(gòu)捕獲;其他的捕獲方式請參閱下文。
JavaScript
錯誤表示 API 被錯誤的使用了,或者正在寫的程序有問題。
一個普通的錯誤對象。和其他的錯誤對象不同,Error
實(shí)例不指示任何為什么錯誤發(fā)生的原因。Error
在它們被實(shí)例化時,會記錄下“堆棧追蹤”信息,并且可以會提供一個錯誤描述。
注意:io.js
會將系統(tǒng)錯誤以及JavaScript
錯誤都封裝為這個類的實(shí)例。
實(shí)例化一個新的Error
對象,并且用提供的message
設(shè)置它的.message
屬性。它的.stack
屬性將會描述new Error
被調(diào)用時程序的這一刻。堆棧追蹤信息隸屬于 V8 堆棧追蹤 API。堆棧追蹤信息只延伸到同步代碼執(zhí)行的開始,或Error.stackTraceLimit
給出的幀數(shù)(number of frames),這取決于哪個更小。
一個在Error()
實(shí)例化時被傳遞的字符串。這個信息會出現(xiàn)在堆棧追蹤信息的第一行。改變這個值將不會改變堆棧追蹤信息的第一行。
這個屬性返回一個代表錯誤被實(shí)例化時程序運(yùn)行的那個點(diǎn)的字符串。
一個堆棧追蹤信息例子:
Error: Things keep happening!
at /home/gbusey/file.js:525:2
at Frobnicator.refrobulate (/home/gbusey/business-logic.js:424:21)
at Actor.<anonymous> (/home/gbusey/actors.js:400:8)
at increaseSynergy (/home/gbusey/actors.js:701:6)
第一行被格式化為<錯誤類名>: <錯誤信息>
,然后是一系列的堆棧信息幀(以“at”
開頭)。每幀都描述了一個最終導(dǎo)致錯誤生成的一次調(diào)用的地點(diǎn)。V8 會試圖去給出每個函數(shù)的名字(通過變量名,函數(shù)名或?qū)ο蠓椒且灿锌赡芩也坏揭粋€合適的名字。如果 V8 不能為函數(shù)定義一個名字,那么那一幀里只會展示出位置信息。否則,被定義的函數(shù)名會顯示在位置信息之前。
幀只會由JavaScript
函數(shù)生成。例如,如果在一個JavaScript
函數(shù)里,同步執(zhí)行了一個叫cheetahify
的 C++ addon
函數(shù),那么堆棧追蹤信息中的幀里將不會有cheetahify
調(diào)用:
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 內(nèi)部的一次調(diào)用(如在[].forEach
中)。plain-filename.js:line:column
,如果幀代表了向io.js
內(nèi)部的一次調(diào)用。/absolute/path/to/file.js:line:column
,如果幀代表了向用戶程序或其依賴的一次調(diào)用。關(guān)鍵的一點(diǎn)是,代表了堆棧信息的字符串只在需要被使用時生成,它是惰性生成的。
堆棧信息的幀數(shù)由 Error.stackTraceLimit
或 當(dāng)前事件循環(huán)的tick
里可用的幀數(shù) 中小的一方?jīng)Q定。
系統(tǒng)級別錯誤被作為增強(qiáng)的Error
實(shí)例生成,參閱下文。
為targetObject
創(chuàng)建一個.stack
屬性,它代表了Error.captureStackTrace
被調(diào)用時,在程序中的位置。
var myObject = {};
Error.captureStackTrace(myObject);
myObject.stack // similar to `new Error().stack`
追蹤信息的第一行,將是targetObject.toString()
的結(jié)果,而不是一個帶有ErrorType:
前綴的信息。
可選的constructorOpt
接收一個函數(shù)。如果指定,所有constructorOpt
以上的幀,包括constructorOpt
,將會被生成的堆棧追蹤信息忽略。
這對于向最終用戶隱藏實(shí)現(xiàn)細(xì)節(jié)十分有用。一個普遍的使用這個參數(shù)的例子:
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
一個決定了堆棧追蹤信息的堆棧幀數(shù)的屬性(不論是由new Error().stack
或由Error.captureStackTrace(obj)
生成)。
初始值是10
??梢员辉O(shè)置為任何有效的JavaScript
數(shù)字,當(dāng)值被改變后,就會影響所有的堆棧追蹤信息的獲取。如果設(shè)置為一個非數(shù)字值,堆棧追蹤將不會獲取任何一幀,并且會在要使用時報(bào)告undefined
。
一個Error
子類,表明了為一個函數(shù)提供的參數(shù)沒有在可接受的值的范圍之內(nèi);不論是在一個數(shù)字范圍之外,或是在一個參數(shù)指定的參數(shù)集合范圍之外。例子:
require('net').connect(-1); // throws RangeError, port should be > 0 && < 65536
io.js
會立刻生成并拋出一個RangeError
實(shí)例 -- 它們是參數(shù)驗(yàn)證的一種形式。
一個Error
子類,表明了提供的參數(shù)不是被允許的類型。例如,為一個期望收到字符串參數(shù)的函數(shù),傳入一個函數(shù)作為參數(shù),將導(dǎo)致一個類型錯誤。
require('url').parse(function() { }); // throws TypeError, since it expected a string
io.js
會立刻生成并拋出一個TypeError
實(shí)例 -- 它們是參數(shù)驗(yàn)證的一種形式。
一個Error
子類,表明了試圖去獲取一個未定義的對象的屬性。大多數(shù)情況下它表明了一個輸入錯誤,或者一個不完整的程序。客戶端代碼可能會生成和傳播這些錯誤,但實(shí)際上只有 V8 會。
doesNotExist; // throws ReferenceError, doesNotExist is not a variable in this program.
ReferenceError
實(shí)例將有一個.arguments
屬性,它是一個包含了一個元素的數(shù)組。這個元素表示沒有被定義的那個變量。
try {
doesNotExist;
} catch(err) {
err.arguments[0] === 'doesNotExist';
}
除非用戶程序是動態(tài)生成并執(zhí)行的,否則,ReferenceErrors
應(yīng)該永遠(yuǎn)被認(rèn)為是程序或其依賴模塊的bug。
一個Error
子類,表明了程序代碼不是合法的JavaScript
。這些錯誤可能只會作為代碼運(yùn)行的結(jié)果生成。代碼運(yùn)行可能是eval
,Function
,require
或vm
的結(jié)果。這些錯誤經(jīng)常表明了一個不完整的程序。
try {
require("vm").runInThisContext("binary ! isNotOk");
} catch(err) {
// err will be a SyntaxError
}
SyntaxError
對于創(chuàng)建它們的上下文來說是不可恢復(fù)的 - 它們僅可能被其他上下文捕獲。
一個JavaScript
“異?!笔且粋€無效操作或throw
聲明所拋出的結(jié)果的值。但是這些值不被要求必須繼承于Error
。所有的由io.js
或JavaScript
運(yùn)行時拋出的異常都必須是Error
實(shí)例。
一些異常在JavaScript
層是無法恢復(fù)的。這些異常通常使一個進(jìn)程掛掉。它們通常無法通過assert()
檢查,或 C++ 層中的abort()
調(diào)用。
系統(tǒng)錯誤在程序運(yùn)行時環(huán)境的響應(yīng)中生成。理想情況下,它們代表了程序能夠處理的操作錯誤。它們在系統(tǒng)調(diào)用級別生成:一個詳盡的錯誤碼列表和它們意義可以通過運(yùn)行man 2 intro
或man 3 errno
在大多數(shù)Unices
中獲得;或在線獲得。
在io.js
中,系統(tǒng)錯誤表現(xiàn)為一個增強(qiáng)的Error
對象 -- 不是完全的子類,而是一個有額外成員的error
實(shí)例。
一個代表了失敗的系統(tǒng)調(diào)用的字符串。
一個代表了錯誤碼的字符串,通常是大寫字母E
,可在man 2 intro
命令的結(jié)果中查閱。
這個列表不詳盡,但是列舉了許多在寫io.js
的過程中普遍發(fā)生的系統(tǒng)錯誤。詳盡的列表可以在這里查閱:http://man7.org/linux/man-pages/man3/errno.3.html
試圖去執(zhí)行一個需要特權(quán)的操作。
通常由文件操作產(chǎn)生;指定的路徑不存在 -- 通過指定的路徑不能找到實(shí)例(文件或目錄)。
試圖以禁止的方式去訪問一個需要權(quán)限的文件。
執(zhí)行一個要求目標(biāo)不存在的操作時,一個已存在文件已經(jīng)是目標(biāo)。
給定的路徑存在,但不是期望的目錄。通常由fs.readdir
產(chǎn)生。
一個操作期望接收一個文件,但給定的路徑是一個文件。
達(dá)到了系統(tǒng)中允許的文件描述符的最大數(shù)量,那么下一個描述符請求,在已存在的最后一個描述符關(guān)閉之前,都不能被滿足。
通常在并行打開太多文件時觸發(fā),特別是在那些將進(jìn)程可用的文件描述符數(shù)量限制得很低的操作系統(tǒng)中(尤其是 OS X)。為了改善這個限制,在同一個 SHELL 中運(yùn)行ulimit -n 2048
命令,再運(yùn)行io.js
進(jìn)程。
向沒有讀取數(shù)據(jù)進(jìn)程的管道,socket 或 FIFO 中執(zhí)行一個寫操作。通常在網(wǎng)絡(luò)和 http 層發(fā)生,表明需要被寫入的遠(yuǎn)程流已經(jīng)被關(guān)閉。
試圖給一個服務(wù)器(net,http 或 https)綁定一個本地地址失敗,因?yàn)榱硪粋€本地系統(tǒng)中的服務(wù)器已經(jīng)使用了那個地址。
連接的雙方被強(qiáng)行關(guān)閉。通常是遠(yuǎn)程socket
超時或重啟的結(jié)果。通常由http
和net
模塊產(chǎn)生。
由于目標(biāo)機(jī)器積極拒絕,沒有連接可以建立。通常是試圖訪問一個不活躍的遠(yuǎn)程主機(jī)的服務(wù)的結(jié)果。
操作的實(shí)例要求是一個空目錄,但目錄不為空 -- 通常由fs.unlink
產(chǎn)生。
因?yàn)楸贿B接方在一段指定內(nèi)未響應(yīng),連接或發(fā)送請求失敗。通常由 http 或 net 產(chǎn)生 -- 經(jīng)常是一個 被連接socket
沒有合適地調(diào)用.end()
方法 的標(biāo)志。
所有的io.js
API 將無效的參數(shù)視作異常 -- 也就是說,如果傳遞了非法的參數(shù),他們會立刻生成并拋出一個error
作為異常,甚至是異步 API 也會。
同步 API(像fs.readFileSync
)將會拋出一個錯誤。拋出值的行為是將值包裝入一個異常。異??梢员皇褂?code>try { } catch(err) { }結(jié)果捕獲。
異步 API 有兩種錯誤傳播機(jī)制;一種代表了單個操作(Node 風(fēng)格的回調(diào)函數(shù)),另一種代表了多個操作(錯誤事件)。
單個操作使用Node風(fēng)格的回調(diào)函數(shù)
-- 一個提供給 API 作為參數(shù)的函數(shù)。Node 風(fēng)格的回調(diào)函數(shù)至少有一個參數(shù) -- error
-- 它可以是null
(如果沒有錯誤發(fā)生)或是Error
實(shí)例。例子:
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 生成的錯誤。一個初學(xué)者的常見錯誤是嘗試在Node 風(fēng)格的回調(diào)函數(shù)中拋出錯誤:
// 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
}
這將不會正常運(yùn)行!在 Node 風(fēng)格的回調(diào)函數(shù)執(zhí)行時,外圍的代碼try { } catch(err) { }
)已經(jīng)退出了。在大多數(shù)情況,在 Node 風(fēng)格的回調(diào)函數(shù)內(nèi)部拋出錯誤會使進(jìn)程掛掉。如果啟用了domain
,它們可以捕獲了被拋出的錯誤;相似的,如果給process.on('uncaughtException')
添加了監(jiān)聽器,那么它也將會捕獲錯誤。
另一個提供錯誤的機(jī)制是error
事件。這常被用在基于流或基于event emitter
的 API 中,它們自身就代表了一系列的異步操作(每一個單一的操作都可能成功或失敗)。如果在錯誤的源頭沒有添加error
事件的監(jiān)聽器,那么error
會被拋出。此時,進(jìn)程會因?yàn)橐粋€未處理的異常而掛掉,除非提供了合適的domains
,或監(jiān)聽了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);
“當(dāng)沒有沒有監(jiān)聽錯誤時會拋出錯誤”這個行為不僅限與io.js
提供的API -- 用戶創(chuàng)建的基于流或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 風(fēng)格的回調(diào)函數(shù)相同,這種方式產(chǎn)生的錯誤也不能被try { } catch(err) { }
捕獲 -- 它們發(fā)生時,外圍的代碼已經(jīng)退出了。