深入理解JavaScript Errors和Stack Traces_第1頁
深入理解JavaScript Errors和Stack Traces_第2頁
深入理解JavaScript Errors和Stack Traces_第3頁
深入理解JavaScript Errors和Stack Traces_第4頁
深入理解JavaScript Errors和Stack Traces_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論