深入理解JavaScript Errors和Stack Traces.docx

上传人:b****6 文档编号:8222659 上传时间:2023-01-30 格式:DOCX 页数:15 大小:44.61KB
下载 相关 举报
深入理解JavaScript Errors和Stack Traces.docx_第1页
第1页 / 共15页
深入理解JavaScript Errors和Stack Traces.docx_第2页
第2页 / 共15页
深入理解JavaScript Errors和Stack Traces.docx_第3页
第3页 / 共15页
深入理解JavaScript Errors和Stack Traces.docx_第4页
第4页 / 共15页
深入理解JavaScript Errors和Stack Traces.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

深入理解JavaScript Errors和Stack Traces.docx

《深入理解JavaScript Errors和Stack Traces.docx》由会员分享,可在线阅读,更多相关《深入理解JavaScript Errors和Stack Traces.docx(15页珍藏版)》请在冰豆网上搜索。

深入理解JavaScript Errors和Stack Traces.docx

深入理解JavaScriptErrors和StackTraces

深入理解JavaScriptErrors和StackTraces

这次我们聊聊Errors和Stacktraces以及如何熟练地使用它们。

很多同学并不重视这些细节,但是这些知识在你写Testing和Error相关的lib的时候是非常有用的。

使用Stacktraces可以清理无用的数据,让你关注真正重要的问题。

同时,你真正理解Errors和它们的属性到底是什么的时候,你将会更有信心的使用它们。

这篇文章在开始的时候看起来比较简单,但当你熟练运用Stacktrace以后则会感到非常复杂。

所以在看难的章节之前,请确保你理解了前面的内容。

一、Stack是如何工作的

在我们谈到Errors之前,我们必须理解Stack是如何工作的。

它其实非常简单,但是在开始之前了解它也是非常必要的。

如果你已经知道了这些,可以略过这一章节。

每当有一个函数调用,就会将其压入栈顶。

在调用结束的时候再将其从栈顶移出。

这种有趣的数据结构叫做“最后一个进入的,将会第一个出去”。

这就是广为所知的LIFO(后进先出)。

举个例子,在函数x的内部调用了函数y,这时栈中就有个顺序先x后y。

我再举另外一个例子,看下面代码:

1.function c() { 

2.    console.log('c'); 

3.} 

4. 

5.function b() { 

6.    console.log('b'); 

7.    c(); 

8.} 

9. 

10.function a() { 

11.    console.log('a'); 

12.    b(); 

13.} 

14. 

15.a(); 

上面的这段代码,当运行a的时候,它会被压到栈顶。

然后,当b在a中被调用的时候,它会被继续压入栈顶,当c在b中被调用的时候,也一样。

在运行c的时候,栈中包含了a,b,c,并且其顺序也是a,b,c。

当c调用完毕时,它会被从栈顶移出,随后控制流回到b。

当b执行完毕后也会从栈顶移出,控制流交还到a。

最后,当a执行完毕后也会从栈中移出。

为了更好的展示这样一种行为,我们用console.trace()来将Stacktrace打印到控制台上来。

通常我们读Stacktraces信息的时候是从上往下读的。

1.function c() { 

2.    console.log('c'); 

3.    console.trace(); 

4.} 

5. 

6.function b() { 

7.    console.log('b'); 

8.    c(); 

9.} 

10. 

11.function a() { 

12.    console.log('a'); 

13.    b(); 

14.} 

15. 

16.a(); 

当我们在NodeREPL服务端执行的时候,会返回如下:

1.Trace 

2.    at c (repl:

3:

9) 

3.    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'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.    at REPLServer.defaultEval (repl.js:

313:

29) 

10.    at bound (domain.js:

280:

14) 

11.    at REPLServer.runBound [as eval] (domain.js:

293:

12) 

从上面我们可以看到,当栈信息从c中打印出来的时候,我看到了a,b和c。

现在,如果在c执行完毕以后,在b中把Stacktrace打印出来,我们可以看到c已经从栈中移出了,栈中只有a和b。

1.function c() { 

2.    console.log('c'); 

3.} 

4. 

5.function b() { 

6.    console.log('b'); 

7.    c(); 

8.    console.trace(); 

9.} 

10. 

11.function a() { 

12.    console.log('a'); 

13.    b(); 

14.} 

15. 

16.a(); 

下面可以看到,c已经不在栈中了,在其执行完以后,从栈中pop出去了。

1.Trace 

2.    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 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.    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 (repl.js:

513:

10) 

概括一下:

当调用时,压入栈顶。

当它执行完毕时,被弹出栈,就是这么简单。

二、Error对象和Error处理

当Error发生的时候,通常会抛出一个Error对象。

Error对象也可以被看做一个Error原型,用户可以扩展其含义,以创建自己的Error对象。

Error.prototype对象通常包含下面属性:

∙constructor-一个错误实例原型的构造函数

∙message-错误信息

∙name-错误名称

这几个都是标准属性,有时不同编译的环境会有其独特的属性。

在一些环境中,例如Node和Firefox,甚至还有stack属性,这里面包含了错误的Stacktrace。

一个Error的堆栈追踪包含了从其构造函数开始的所有堆栈帧。

如果你想要学习一个Error对象的特殊属性,我强烈建议你看一下在MDN上的这篇文章。

要抛出一个Error,你必须使用throw关键字。

为了catch一个抛出的Error,你必须把可能抛出Error的代码用try块包起来。

然后紧跟着一个catch块,catch块中通常会接受一个包含了错误信息的参数。

和在Java中类似,不论在try中是否抛出Error,JavaScript中都允许你在try/catch块后面紧跟着一个finally块。

不论你在try中的操作是否生效,在你操作完以后,都用finally来清理对象,这是个编程的好习惯。

介绍到现在的知识,可能对于大部分人来说,都是已经掌握了的,那么现在我们就进行更深入一些的吧。

使用try块时,后面可以不跟着catch块,但是必须跟着finally块。

所以我们就有三种不同形式的try语句:

∙try...catch

∙try...finally

∙try...catch...finally

Try语句也可以内嵌在一个try语句中,如:

1.try { 

2.    try { 

3.        // 这里抛出的Error,将被下面的catch获取到 

4.        throw new Error('Nested error.');  

5.    } catch (nestedErr) { 

6.        // 这里会打印出来 

7.        console.log('Nested catch'); 

8.    } 

9.} catch (err) { 

10.    console.log('This will not run.'); 

11.} 

你也可以把try语句内嵌在catch和finally块中:

1.try { 

2.    throw new Error('First error'); 

3.} catch (err) { 

4.    console.log('First catch running'); 

5.    try { 

6.        throw new Error('Second error'); 

7.    } catch (nestedErr) { 

8.        console.log('Second catch running.'); 

9.    } 

10.} 

1.try { 

2.    console.log('The try block is running...'); 

3.} finally { 

4.    try { 

5.        throw new Error('Error inside finally.'); 

6.    } catch (err) { 

7.        console.log('Caught an error inside the finally block.'); 

8.    } 

9.} 

这里给出另外一个重要的提示:

你可以抛出非Error对象的值。

尽管这看起来很炫酷,很灵活,但实际上这个用法并不好,尤其在一个开发者改另一个开发者写的库的时候。

因为这样代码没有一个标准,你不知道其他人会抛出什么信息。

这样的话,你就不能简单的相信抛出的Error信息了,因为有可能它并不是Error信息,而是一个字符串或者一个数字。

另外这也导致了如果你需要处理Stacktrace或者其他有意义的元数据,也将变的很困难。

例如给你下面这段代码:

1.function runWithoutThrowing(func) { 

2.    try { 

3.        func(); 

4.    } catch (e) { 

5.        console.log('There was an error, but I will not throw it.'); 

6.        console.log('The error\'s message was:

 ' + e.message) 

7.    } 

8.} 

9. 

10.function funcThatThrowsError() { 

11.    throw new TypeError('I am a TypeError.'); 

12.} 

13. 

14.runWithoutThrowing(funcThatThrowsError); 

这段代码,如果其他人传递一个带有抛出Error对象的函数给runWithoutThrowing函数的话,将完美运行。

然而,如果他抛出一个String类型的话,则情况就麻烦了。

1.function runWithoutThrowing(func) { 

2.    try { 

3.        func(); 

4.    } catch (e) { 

5.        console.log('There was an error, but I will not throw it.'); 

6.        console.log('The error\'s message was:

 ' + e.message) 

7.    } 

8.} 

9. 

10.function funcThatThrowsString() { 

11.    throw 'I am a String.'; 

12.} 

13. 

14.runWithoutThrowing(funcThatThrowsString); 

可以看到这段代码中,第二个console.log会告诉你这个Error信息是undefined。

这现在看起来不是很重要,但是如果你需要确定是否这个Error中确实包含某个属性,或者用另一种方式处理Error的特殊属性,那你就需要多花很多的功夫了。

另外,当抛出一个非Error对象的值时,你没有访问Error对象的一些重要的数据,比如它的堆栈,而这在一些编译环境中是一个非常重要的Error对象属性。

Error还可以当做其他普通对象一样使用,你并不需要抛出它。

这就是为什么它通常作为回调函数的第一个参数,就像fs.readdir函数这样:

1.const fs = require('fs'); 

2. 

3.fs.readdir('/example/i-do-not-exist', function callback(err, dirs) { 

4.    if (err instanceof Error) { 

5.        // 'readdir'将会抛出一个异常,因为目录不存在 

6.        // 我们可以在我们的回调函数中使用 Error 对象 

7.        console.log('Error Message:

 ' + err.message); 

8.        console.log('See?

 We can use  Errors  without using try statements.'); 

9.    } else { 

10.        console.log(dirs); 

11.    } 

12.}); 

最后,你也可以在promise被reject的时候使用Error对象,这使得处理promisereject变得很简单。

1.new Promise(function(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.    if (err instanceof Error) { 

7.        console.log('The promise was rejected with an error.'); 

8.        console.log('Error Message:

 ' + err.message); 

9.    } 

10.}); 

三、使用StackTrace

ok,那么现在,你们所期待的部分来了:

如何使用堆栈追踪。

这一章专门讨论支持Error.captureStackTrace的环境,如:

NodeJS。

Error.captureStackTrace函数的第一个参数是一个object对象,第二个参数是一个可选的function。

捕获堆栈跟踪所做的是要捕获当前堆栈的路径(这是显而易见的),并且在object对象上创建一个stack属性来存储它。

如果提供了第二个function参数,那么这个被传递的函数将会被看成是本次堆栈调用的终点,本次堆栈跟踪只会展示到这个函数被调用之前。

我们来用几个例子来更清晰的解释下。

我们将捕获当前堆栈路径并且将其存储到一个普通object对象中。

1.const myObj = {}; 

2. 

3.function c() { 

4.} 

5. 

6.function b() { 

7.    // 这里存储当前的堆栈路径,保存到myObj中 

8.    Error.captureStackTrace(myObj); 

9.    c(); 

10.} 

11. 

12.function a() { 

13.    b(); 

14.} 

15. 

16.// 首先调用这些函数 

17.a(); 

18. 

19.// 这里,我们看一下堆栈路径往 myObj.stack 中存储了什么 

20.console.log(myObj.stack); 

21. 

22.// 这里将会打印如下堆栈信息到控制台 

23.//    at b (repl:

3:

7) <-- Since it was called inside B, the B call is the last entry in the stack 

24.//    at a (repl:

2:

1) 

25.//    at repl:

1:

1 <-- Node internals below this line 

26.//    at realRunInThisContextScript (vm.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.//    at bound (domain.js:

280:

14) 

31.//    at REPLServer.runBound [as eval] (domain.js:

293:

12) 

32.//    at REPLServer.onLine (repl.js:

513:

10) 

我们从上面的例子中可以看到,我们首先调用了a(a被压入栈),然后从a的内部调用了b(b被压入栈,并且在a的上面)。

在b中,我们捕获到了当前堆栈路径并且将其存储在了myObj中。

这就是为什么打印在控制台上的只有a和b,而且是下面a上面b。

好的,那么现在,我们传递第二个参数到Error.captureStackTrace看看会发生什么?

1.const myObj = {}; 

2. 

3.function d() { 

4.    // 这里存储当前的堆栈路径,保存到myObj中 

5.    // 这次我们隐藏包含b在内的b以后的所有堆栈帧 

6.    Error.captureStackTrace(myObj, b); 

7.} 

8. 

9.function c() { 

10.    d(); 

11.} 

12. 

13.function b() { 

14.    c(); 

15.} 

16. 

17.function a() { 

18.    b(); 

19.} 

20. 

21.// 首先调用这些函数 

22.a(); 

23. 

24.// 这里,我们看一下堆栈路径往 myObj.stack 中存储了什么 

25.console.log(myObj.stack); 

26. 

27.// 这里将会打印如下堆栈信息到控制台 

28.//    at 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.//    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.defaultEval (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.//    at emitOne (events.js:

101:

20) 

当我们传递b到Error.captureStackTraceFunction里时,它隐藏了b和在它以上的所有堆栈帧。

这就是为什么堆栈路径里只有a的原因。

看到这,你可能会问这样一个问题:

“为什么这是有用的呢?

”。

它之所以有用,是因为你可以隐藏所有的内部实现细节,而这些细节其他开发者调用的时候并不需要知道。

例如,在Chai中,我们用这种方法对我们代码的调用者屏蔽了不相关的实现细节。

四、真实场景中的StackTrace处理

正如我在上一节中提到的,Chai用栈处理技术使得堆栈路径和调用者更加相关,这里是我们如何实现它的。

首先,让我们来看一下当一个Assertion失败的时候,AssertionError的构造函数做了什么。

1.// 'ssfi'代表"起始堆栈函数",它是移除其他不相关堆栈帧的起始标记 

2.function AssertionError (message, _props, ssf) { 

3.  var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') 

4.    , props = extend(_props || {}); 

5. 

6.  // 默认值 

7.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 冶金矿山地质

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1