版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、深入理解JavaScript Errors和Stack Traces這次我們聊聊 Errors 和 Stack traces 以及如何熟練地使用它們。很多同學(xué)并不重視這些細(xì)節(jié),但是這些知識(shí)在你寫 Testing 和 Error 相關(guān)的 lib 的時(shí)候是非常有用的。使用 Stack traces 可以清理無用的數(shù)據(jù),讓你關(guān)注真正重要的問題。同時(shí),你真正理解 Errors 和它們的屬性到底是什么的時(shí)候,你將會(huì)更有信心的使用它們。這篇文章在開始的時(shí)候看起來比較簡單,但當(dāng)你熟練運(yùn)用 Stack trace 以后則會(huì)感到非常復(fù)雜。所以在看難的章節(jié)之前,請確保你理解了前面的內(nèi)容。一、Stack是如何工作的
2、在我們談到 Errors 之前,我們必須理解 Stack 是如何工作的。它其實(shí)非常簡單,但是在開始之前了解它也是非常必要的。如果你已經(jīng)知道了這些,可以略過這一章節(jié)。每當(dāng)有一個(gè)函數(shù)調(diào)用,就會(huì)將其壓入棧頂。在調(diào)用結(jié)束的時(shí)候再將其從棧頂移出。這種有趣的數(shù)據(jù)結(jié)構(gòu)叫做“最后一個(gè)進(jìn)入的,將會(huì)第一個(gè)出去”。這就是廣為所知的 LIFO(后進(jìn)先出)。舉個(gè)例子,在函數(shù) x 的內(nèi)部調(diào)用了函數(shù) y,這時(shí)棧中就有個(gè)順序先 x 后 y。我再舉另外一個(gè)例子,看下面代碼:1. function c() 2. console.log('c&
3、#39;); 3. 4. 5. function b() 6. console.log('b'); 7. c(); 8. 9. 10. function a() 11. console.log('a'); 12. b();&
4、#160;13. 14. 15. a(); 上面的這段代碼,當(dāng)運(yùn)行 a 的時(shí)候,它會(huì)被壓到棧頂。然后,當(dāng) b 在 a 中被調(diào)用的時(shí)候,它會(huì)被繼續(xù)壓入棧頂,當(dāng) c 在 b 中被調(diào)用的時(shí)候,也一樣。在運(yùn)行 c 的時(shí)候,棧中包含了 a,b,c,并且其順序也是 a,b,c。當(dāng) c 調(diào)用完畢時(shí),它會(huì)被從棧頂移出,隨后控制流回到 b。當(dāng) b 執(zhí)行完畢后也會(huì)從棧頂移出,控制流交還到 a。最后,當(dāng) a 執(zhí)行完畢后也會(huì)從棧中移出。為了更好的展示這樣一種行為,我們用 console.trace() 來將 Stack trace 打印到控制臺(tái)上來。通常我們讀 Stack trace
5、s 信息的時(shí)候是從上往下讀的。1. function c() 2. console.log('c'); 3. console.trace(); 4. 5. 6. function b() 7. console.log('b'); 8. c(); 9
6、. 10. 11. function a() 12. console.log('a'); 13. b(); 14. 15. 16. a(); 當(dāng)我們在 Node REPL 服務(wù)端執(zhí)行的時(shí)候,會(huì)返回如下:1. Trace 2. at c (repl:3:9) 3. &
7、#160; at b (repl:3:1) 4. at a (repl:3:1) 5. at repl:1:1 / <- For now feel free to ignore anything below this point, these are Node'
8、;s internals 6. at realRunInThisContextScript (vm.js:22:35) 7. at sigintHandlersWrap (vm.js:98:12) 8. at ContextifyScript.Script.runInThisContext (vm.js:24:12) 9.
9、0; at REPLServer.defaultEval (repl.js:313:29) 10. at bound (domain.js:280:14) 11. at REPLServer.runBound as eval (domain.js:293:12) 從上面我們可以看到,當(dāng)棧信息從 c 中打印出來的時(shí)候,我看到了 a,b 和 c?,F(xiàn)在,如果在 c 執(zhí)行完畢以
10、后,在 b 中把 Stack trace 打印出來,我們可以看到 c 已經(jīng)從棧中移出了,棧中只有 a 和 b。1. function c() 2. console.log('c'); 3. 4. 5. function b() 6. console.log('b'); 7. c(); 8. &
11、#160; console.trace(); 9. 10. 11. function a() 12. console.log('a'); 13. b(); 14. 15. 16. a(); 下面可以看到,c 已經(jīng)不在棧中了,在其執(zhí)行完以后,從棧中 pop 出去了。1. Trace 2.
12、at b (repl:4:9) 3. at a (repl:3:1) 4. at repl:1:1 / <- For now feel free to ignore anything below this point, these are Node's
13、60;internals 5. at realRunInThisContextScript (vm.js:22:35) 6. at sigintHandlersWrap (vm.js:98:12) 7. at ContextifyScript.Script.runInThisContext (vm.js:24:12) 8.
14、60; at REPLServer.defaultEval (repl.js:313:29) 9. at bound (domain.js:280:14) 10. at REPLServer.runBound as eval (domain.js:293:12) 11. at REPLServer.onLine (
15、repl.js:513:10) 概括一下:當(dāng)調(diào)用時(shí),壓入棧頂。當(dāng)它執(zhí)行完畢時(shí),被彈出棧,就是這么簡單。二、Error 對象和 Error 處理當(dāng) Error 發(fā)生的時(shí)候,通常會(huì)拋出一個(gè) Error 對象。Error 對象也可以被看做一個(gè) Error 原型,用戶可以擴(kuò)展其含義,以創(chuàng)建自己的 Error 對象。Etotype 對象通常包含下面屬性:· constructor - 一個(gè)錯(cuò)誤實(shí)例原型的構(gòu)造函數(shù)· message - 錯(cuò)誤信息· name - 錯(cuò)誤名稱這幾個(gè)都是標(biāo)準(zhǔn)屬性,有時(shí)不同編譯的環(huán)境會(huì)有其獨(dú)特的屬性。在一些環(huán)境中,例如 Nod
16、e 和 Firefox,甚至還有 stack 屬性,這里面包含了錯(cuò)誤的 Stack trace。一個(gè) Error 的堆棧追蹤包含了從其構(gòu)造函數(shù)開始的所有堆棧幀。如果你想要學(xué)習(xí)一個(gè) Error 對象的特殊屬性,我強(qiáng)烈建議你看一下在MDN上的這篇文章。要拋出一個(gè) Error,你必須使用 throw 關(guān)鍵字。為了 catch 一個(gè)拋出的 Error,你必須把可能拋出 Error 的代碼用 try 塊包起來。然后緊跟著一個(gè) catch 塊,catch 塊中通常會(huì)接受一個(gè)包含了錯(cuò)誤信息的參數(shù)。和在 Java 中類似,不論在 try 中是否拋出 Error, JavaScript 中都允許你在 try/c
17、atch 塊后面緊跟著一個(gè) finally 塊。不論你在 try 中的操作是否生效,在你操作完以后,都用 finally 來清理對象,這是個(gè)編程的好習(xí)慣。介紹到現(xiàn)在的知識(shí),可能對于大部分人來說,都是已經(jīng)掌握了的,那么現(xiàn)在我們就進(jìn)行更深入一些的吧。使用 try 塊時(shí),后面可以不跟著 catch 塊,但是必須跟著 finally 塊。所以我們就有三種不同形式的 try 語句:· try.catch· try.finally· try.catch.finallyTry 語句也可以內(nèi)嵌在一個(gè) try 語句中,如:1. try 2.
18、60; try 3. / 這里拋出的Error,將被下面的catch獲取到 4. throw new Error('Nested error.'); 5. catch (nestedErr)
19、160;6. / 這里會(huì)打印出來 7. console.log('Nested catch'); 8. 9. catch (err) 10. console.log('This wi
20、ll not run.'); 11. 你也可以把 try 語句內(nèi)嵌在 catch 和 finally 塊中:1. try 2. throw new Error('First error'); 3. catch (err) 4. console.log('First catch running')
21、; 5. try 6. throw new Error('Second error'); 7. catch (nestedErr) 8. console.log('Seco
22、nd catch running.'); 9. 10. 1. try 2. console.log('The try block is running.'); 3. finally 4. try 5.
23、 throw new Error('Error inside finally.'); 6. catch (err) 7. console.log('Caught an error inside the finally block.&
24、#39;); 8. 9. 這里給出另外一個(gè)重要的提示:你可以拋出非 Error 對象的值。盡管這看起來很炫酷,很靈活,但實(shí)際上這個(gè)用法并不好,尤其在一個(gè)開發(fā)者改另一個(gè)開發(fā)者寫的庫的時(shí)候。因?yàn)檫@樣代碼沒有一個(gè)標(biāo)準(zhǔn),你不知道其他人會(huì)拋出什么信息。這樣的話,你就不能簡單的相信拋出的 Error 信息了,因?yàn)橛锌赡芩⒉皇?Error 信息,而是一個(gè)字符串或者一個(gè)數(shù)字。另外這也導(dǎo)致了如果你需要處理 Stack trace 或者其他有意義的元數(shù)據(jù),也將變的很困難。例如給你下面這段代碼:1. function ru
25、nWithoutThrowing(func) 2. try 3. func(); 4. catch (e) 5. console.log('There was an error
26、, but I will not throw it.'); 6. console.log('The error's message was: ' + e.message) 7. 8. 9. 10. function funcThatThr
27、owsError() 11. throw new TypeError('I am a TypeError.'); 12. 13. 14. runWithoutThrowing(funcThatThrowsError); 這段代碼,如果其他人傳遞一個(gè)帶有拋出 Error 對象的函數(shù)給 runWithoutThrowing 函數(shù)的話,將完美運(yùn)行。然而,如果他拋出一個(gè) String 類型的話,則情況就麻煩了。1. func
28、tion runWithoutThrowing(func) 2. try 3. func(); 4. catch (e) 5. console.log('There was a
29、n error, but I will not throw it.'); 6. console.log('The error's message was: ' + e.message) 7. 8. 9. 10. function
30、;funcThatThrowsString() 11. throw 'I am a String.' 12. 13. 14. runWithoutThrowing(funcThatThrowsString); 可以看到這段代碼中,第二個(gè) console.log 會(huì)告訴你這個(gè) Error 信息是 undefined。這現(xiàn)在看起來不是很重要,但是如果你需要確定是否這個(gè) Error 中確實(shí)包含某個(gè)屬性,或者用另一種方式處理 Erro
31、r 的特殊屬性,那你就需要多花很多的功夫了。另外,當(dāng)拋出一個(gè)非 Error 對象的值時(shí),你沒有訪問 Error 對象的一些重要的數(shù)據(jù),比如它的堆棧,而這在一些編譯環(huán)境中是一個(gè)非常重要的 Error 對象屬性。Error 還可以當(dāng)做其他普通對象一樣使用,你并不需要拋出它。這就是為什么它通常作為回調(diào)函數(shù)的第一個(gè)參數(shù),就像 fs.readdir 函數(shù)這樣:1. const fs = require('fs'); 2. 3. fs.readdir('/example/i-do-not-exist', func
32、tion callback(err, dirs) 4. if (err instanceof Error) 5. / 'readdir'將會(huì)拋出一個(gè)異常,因?yàn)槟夸洸淮嬖?#160;6. / 我們可以在我們的回調(diào)函數(shù)中使用
33、;Error 對象 7. console.log('Error Message: ' + err.message); 8. console.log('See? We can use Errors without using
34、0;try statements.'); 9. else 10. console.log(dirs); 11. 12. ); 最后,你也可以在 promise 被 reject 的時(shí)候使用 Error 對象,這使得處理 promise reject 變得很簡單。1. new Promise(functi
35、on(resolve, reject) 2. reject(new Error('The promise was rejected.'); 3. ).then(function() 4. console.log('I am an error.'); 5. ).catch(function(err) 6.
36、 if (err instanceof Error) 7. console.log('The promise was rejected with an error.'); 8. console.log('Error&
37、#160;Message: ' + err.message); 9. 10. ); 三、使用 Stack Traceok,那么現(xiàn)在,你們所期待的部分來了:如何使用堆棧追蹤。這一章專門討論支持 Error.captureStackTrace 的環(huán)境,如:NodeJS。Error.captureStackTrace 函數(shù)的第一個(gè)參數(shù)是一個(gè) object 對象,第二個(gè)參數(shù)是一個(gè)可選的 function。捕獲堆棧跟蹤所做的是要捕獲當(dāng)前堆棧的路徑(這是顯而易見的),并且在 object
38、對象上創(chuàng)建一個(gè) stack 屬性來存儲(chǔ)它。如果提供了第二個(gè) function 參數(shù),那么這個(gè)被傳遞的函數(shù)將會(huì)被看成是本次堆棧調(diào)用的終點(diǎn),本次堆棧跟蹤只會(huì)展示到這個(gè)函數(shù)被調(diào)用之前。我們來用幾個(gè)例子來更清晰的解釋下。我們將捕獲當(dāng)前堆棧路徑并且將其存儲(chǔ)到一個(gè)普通 object 對象中。1. const myObj = 2. 3. function c() 4. 5. 6. function b() 7. /
39、60;這里存儲(chǔ)當(dāng)前的堆棧路徑,保存到myObj中 8. Error.captureStackTrace(myObj); 9. c(); 10. 11. 12. function a() 13. b(); 14. 15. 16. / 首先調(diào)用這些函數(shù) 17. a(); 18. 19. /
40、0;這里,我們看一下堆棧路徑往 myObj.stack 中存儲(chǔ)了什么 20. console.log(myObj.stack); 21. 22. / 這里將會(huì)打印如下堆棧信息到控制臺(tái) 23. / at b (repl:3:7) <- Since it was called inside B, the B call is the
41、0;last entry in the stack 24. / at a (repl:2:1) 25. / at repl:1:1 <- Node internals below this line 26. / at realRunInThisContextScript (v
42、m.js:22:35) 27. / at sigintHandlersWrap (vm.js:98:12) 28. / at ContextifyScript.Script.runInThisContext (vm.js:24:12) 29. / at REPLServer.defaultEval (repl.js:313:29) 30. /
43、60; at bound (domain.js:280:14) 31. / at REPLServer.runBound as eval (domain.js:293:12) 32. / at REPLServer.onLine (repl.js:513:10) 我們從上面的例子中可以看到,我們首先調(diào)用了a(a被壓入棧),然后從a的內(nèi)部調(diào)用了b(b被壓入棧,并且在a的上面)
44、。在b中,我們捕獲到了當(dāng)前堆棧路徑并且將其存儲(chǔ)在了 myObj 中。這就是為什么打印在控制臺(tái)上的只有a和b,而且是下面a上面b。好的,那么現(xiàn)在,我們傳遞第二個(gè)參數(shù)到 Error.captureStackTrace 看看會(huì)發(fā)生什么?1. const myObj = 2. 3. function d() 4. / 這里存儲(chǔ)當(dāng)前的堆棧路徑,保存到myObj中 5. / 這次我們隱藏包含b在內(nèi)
45、的b以后的所有堆棧幀 6. Error.captureStackTrace(myObj, b); 7. 8. 9. function c() 10. d(); 11. 12. 13. function b() 14. c(); 15. 16. 17. function&
46、#160;a() 18. b(); 19. 20. 21. / 首先調(diào)用這些函數(shù) 22. a(); 23. 24. / 這里,我們看一下堆棧路徑往 myObj.stack 中存儲(chǔ)了什么 25. console.log(myObj.stack); 26. 27. / 這里將會(huì)打印如下堆棧信息到控制臺(tái) 28. / at
47、;a (repl:2:1) <- As you can see here we only get frames before b was called 29. / at repl:1:1 <- Node internals below this line 30. /
48、 at realRunInThisContextScript (vm.js:22:35) 31. / at sigintHandlersWrap (vm.js:98:12) 32. / at ContextifyScript.Script.runInThisContext (vm.js:24:12) 33. / at REPLServer.defau
49、ltEval (repl.js:313:29) 34. / at bound (domain.js:280:14) 35. / at REPLServer.runBound as eval (domain.js:293:12) 36. / at REPLServer.onLine (repl.js:513:10) 37. /
50、60; at emitOne (events.js:101:20) 當(dāng)我們傳遞 b 到 Error.captureStackTraceFunction 里時(shí),它隱藏了 b 和在它以上的所有堆棧幀。這就是為什么堆棧路徑里只有a的原因。看到這,你可能會(huì)問這樣一個(gè)問題:“為什么這是有用的呢?”。它之所以有用,是因?yàn)槟憧梢噪[藏所有的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),而這些細(xì)節(jié)其他開發(fā)者調(diào)用的時(shí)候并不需要知道。例如,在 Chai 中,我們用這種方法對我們代碼的調(diào)用者屏蔽了不相關(guān)的實(shí)現(xiàn)細(xì)節(jié)。四、真實(shí)場景中的 Stack Trace 處理正如我在上一節(jié)中提到的,C
51、hai 用棧處理技術(shù)使得堆棧路徑和調(diào)用者更加相關(guān),這里是我們?nèi)绾螌?shí)現(xiàn)它的。首先,讓我們來看一下當(dāng)一個(gè) Assertion 失敗的時(shí)候,AssertionError 的構(gòu)造函數(shù)做了什么。1. / 'ssfi'代表"起始堆棧函數(shù)",它是移除其他不相關(guān)堆棧幀的起始標(biāo)記 2. function AssertionError (message, _props, ssf) 3. var extend = exclude('nam
52、e', 'message', 'stack', 'constructor', 'toJSON') 4. , props = extend(_props | ); 5. 6. / 默認(rèn)值 7. this.message = message | '
53、;Unspecified AssertionError' 8. this.showDiff = false; 9. 10. / 從屬性中copy 11. for (var key in props) 12. thiskey = propskey; 13. 14.
54、 15. / 這里是和我們相關(guān)的 16. / 如果提供了起始堆棧函數(shù),那么我們從當(dāng)前堆棧路徑中獲取到, 17. / 并且將其傳遞給'captureStackTrace',以保證移除其后的所有幀 18. ssfssf = ssf | arguments.callee; 19. if (ssf &&
55、;Error.captureStackTrace) 20. Error.captureStackTrace(this, ssf); 21. else 22. / 如果沒有提供起始堆棧函數(shù),那么使用原始堆棧 23. try 24. throw&
56、#160;new Error(); 25. catch(e) 26. this.stack = e.stack; 27. 28. 29. 正如你在上面可以看到的,我們使用了 Error.captureStackTrace 來捕獲堆棧路徑,并且把它存儲(chǔ)在我們所創(chuàng)建的一個(gè) Assertion
57、Error 實(shí)例中。然后傳遞了一個(gè)起始堆棧函數(shù)進(jìn)去(用if判斷如果存在則傳遞),這樣就從堆棧路徑中移除掉了不相關(guān)的堆棧幀,不顯示一些內(nèi)部實(shí)現(xiàn)細(xì)節(jié),保證了堆棧信息的“清潔”。在我們繼續(xù)看下面的代碼之前,我要先告訴你 addChainableMethod 都做了什么。它添加所傳遞的可以被鏈?zhǔn)秸{(diào)用的方法到 Assertion,并且用包含了 Assertion 的方法標(biāo)記 Assertion 本身。用ssfi(表示起始堆棧函數(shù)指示器)這個(gè)名字記錄。這意味著當(dāng)前 Assertion 就是堆棧的最后一幀,就是說不會(huì)再多顯示任何 Chai 項(xiàng)目中的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)了。我在這里就不多列出來其整個(gè)代碼了,里面用了很
58、多 trick 的方法,但是如果你想了解更多,可以從 這個(gè)鏈接 里獲取到。在下面的代碼中,展示了 lengthOf 的 Assertion 的邏輯,它是用來檢查一個(gè)對象的確定長度的。我們希望調(diào)用我們函數(shù)的開發(fā)者這樣來使用:expect('foo', 'bar').to.have.lengthOf(2)。1. function assertLength (n, msg) 2. if (msg) flag(this, 'messa
59、ge', msg); 3. var obj = flag(this, 'object') 4. , ssfi = flag(this, 'ssfi'); 5. 6. / 密切關(guān)注這一行 7.
60、 new Assertion(obj, msg, ssfi, true).perty('length'); 8. var len = obj.length; 9. 10. / 這一行也是相關(guān)的 11. this.assert( 12.
61、160; len = n 13. , 'expected #this to have a length of #exp but got #act' 14.
62、160; , 'expected #this to not have a length of #act' 15. , n 16. , len 17. ); 18. 19.
63、 20. Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain); 下面是 this.assert 方法的代碼:1. function assertLength (n, msg) 2. if (msg) flag(this, 'message', msg); 3.
64、0; var obj = flag(this, 'object') 4. , ssfi = flag(this, 'ssfi'); 5. 6. / 密切關(guān)注這一行 7. new Assertion(obj,
65、60;msg, ssfi, true).perty('length'); 8. var len = obj.length; 9. 10. / 這一行也是相關(guān)的 11. this.assert( 12. len = n 13. , 'expected #this to have a length of #exp but got #act
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年房屋出租押金條款合同
- 2024年建筑項(xiàng)目施工質(zhì)量控制合同
- 2024年度產(chǎn)品代理銷售合同服務(wù)內(nèi)容
- 2024年技術(shù)咨詢合同標(biāo)的及其屬性
- 2024年房產(chǎn)交易代理權(quán)協(xié)議
- 2024年房產(chǎn)交易合同公證
- 2024年度銷售代理合同代理區(qū)域及銷售目標(biāo)
- 2024幕墻工程碳排放交易合同
- 2024年保密期限合同
- DB4114T 196-2023 冬小麥節(jié)水栽培技術(shù)規(guī)程
- 《建筑施工技術(shù)》課后習(xí)題答案(大學(xué)期末復(fù)習(xí)資料)
- 公司環(huán)境行政處罰事件處置預(yù)案
- 廣東開放大學(xué)風(fēng)險(xiǎn)投資(本2022春)-練習(xí)4答案
- DB65∕T 3253-2020 建筑消防設(shè)施質(zhì)量檢測評定規(guī)程
- 二年級(jí)蘇教版數(shù)學(xué)上冊《7的乘法口訣》教案(公開課三稿)
- (完整PPT)半導(dǎo)體物理與器件物理課件
- ASTM B366 B366M-20 工廠制造的變形鎳和鎳合金配件標(biāo)準(zhǔn)規(guī)范
- JIS G4304-2021 熱軋不銹鋼板材、薄板材和帶材
- 2022年中級(jí)經(jīng)濟(jì)師-人力資源管理專業(yè)押題模擬試卷3套及答案解析
- 小學(xué)綜合實(shí)踐活動(dòng)《認(rèn)識(shí)校園植物》優(yōu)秀PPT課件
- XRD在薄膜材料研究中應(yīng)用
評論
0/150
提交評論