-
Notifications
You must be signed in to change notification settings - Fork 442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[译]JavaScript错误处理和堆栈追踪 #49
Comments
Hi! Thanks for translating this! 😄 |
The article is great! I learn a lot about JavaScript Errors Handling & function call stack from it. @lucasfcosta |
Thanks for explain |
This article is fantastic! 🚀 |
This is awesome. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
有时我们会忽略错误处理和堆栈追踪的一些细节, 但是这些细节对于写与测试或错误处理相关的库来说是非常有用的. 例如这周, 对于 Chai 就有一个非常棒的PR, 该PR极大地改善了我们处理堆栈的方式, 当用户的断言失败的时候, 我们会给予更多的提示信息(帮助用户进行定位).
合理地处理堆栈信息能使你清除无用的数据, 而只专注于有用的数据. 同时, 当更好地理解
Errors
对象及其相关属性之后, 能有助于你更充分地利用Errors
.(函数的)调用栈是怎么工作的
在谈论错误之前, 先要了解下(函数的)调用栈的原理:
当有一个函数被调用的时候, 它就被压入到堆栈的顶部, 该函数运行完成之后, 又会从堆栈的顶部被移除.
堆栈的数据结构就是后进先出, 以 LIFO (last in, first out) 著称.
例如:
在上述的示例中, 当函数
a
运行时, 其会被添加到堆栈的顶部. 然后, 当函数b
在函数a
的内部被调用时, 函数b
会被压入到堆栈的顶部. 当函数c
在函数b
的内部被调用时也会被压入到堆栈的顶部.当函数
c
运行时, 堆栈中就包含了a
,b
和c
(按此顺序).当函数
c
运行完毕之后, 就会从堆栈的顶部被移除, 然后函数调用的控制流就回到函数b
. 函数b
运行完之后, 也会从堆栈的顶部被移除, 然后函数调用的控制流就回到函数a
. 最后, 函数a
运行完成之后也会从堆栈的顶部被移除.为了更好地在demo中演示堆栈的行为, 可以使用
console.trace()
在控制台输出当前的堆栈数据. 同时, 你要以从上至下的顺序阅读输出的堆栈数据.在 Node 的 REPL 模式中运行上述代码会得到如下输出:
正如所看到的, 当从函数
c
中输出时, 堆栈中包含了函数a
,b
以及c
.如果在函数
c
运行完成之后, 在函数b
中输出当前的堆栈数据, 就会看到函数c
已经从堆栈的顶部被移除, 此时堆栈中仅包括函数a
和b
.正如所看到的, 函数
c
运行完成之后, 已经从堆栈的顶部被移除.Error对象和错误处理
当程序运行出现错误时, 通常会抛出一个
Error
对象.Error
对象可以作为用户自定义错误对象继承的原型.Error.prototype
对象包含如下属性:constructor
--指向实例的构造函数message
--错误信息name
--错误的名字(类型)上述是
Error.prototype
的标准属性, 此外, 不同的运行环境都有其特定的属性. 在例如 Node, Firefox, Chrome, Edge, IE 10+, Opera 以及 Safari 6+ 这样的环境中,Error
对象具备stack
属性, 该属性包含了错误的堆栈轨迹. 一个错误实例的堆栈轨迹包含了自构造函数之后的所有堆栈结构.如果想了解更多关于
Error
对象的特定属性, 可以阅读 MDN 上的这篇文章.为了抛出一个错误, 必须使用
throw
关键字. 为了catch
一个抛出的错误, 必须使用try...catch
包含可能跑出错误的代码. Catch的参数是被跑出的错误实例.如 Java 一样, JavaScript 也允许在
try/catch
之后使用finally
关键字. 在处理完错误之后, 可以在finally
语句块作一些清除工作.在语法上, 你可以使用
try
语句块而其后不必跟着catch
语句块, 但必须跟着finally
语句块. 这意味着有三种不同的try
语句形式:try...catch
try...finally
try...catch...finally
Try语句内还可以在嵌入
try
语句:也可以在
catch
或finally
中嵌入try
语句:需要重点说明一下的是在抛出错误时, 可以只抛出一个简单值而不是
Error
对象. 尽管这看起来看酷并且是允许的, 但这并不是一个推荐的做法, 尤其是对于一些需要处理他人代码的库和框架的开发者, 因为没有标准可以参考, 也无法得知会从用户那里得到什么. 你不能信任用户会抛出Error
对象, 因为他们可能不会这么做, 而是简单的抛出一个字符串或者数值. 这也意味着很难去处理堆栈信息和其它元信息.例如:
如果用户传递给函数
runWithoutThrowing
的参数抛出了一个错误对象, 上面的代码能正常捕获错误. 然后, 如果是抛出一个字符串, 就会碰到一些问题了:现在第二个
console.log
会输出undefined. 这看起来不是很重要, 但如果你需要确保Error
对象有一个特定的属性或者用另一种方式来处理Error
对象的特定属性(例如 Chai的throws断言的做法), 你就得做大量的工作来确保程序的正确运行.同时, 如果抛出的不是
Error
对象, 也就获取不到stack
属性.Errors 也可以被作为其它对象, 你也不必抛出它们, 这也是为什么大多数回调函数把 Errors 作为第一个参数的原因. 例如:
最后,
Error
对象也可以用于 rejected promise, 这使得很容易处理 rejected promise:处理堆栈
这一节是针对支持
Error.captureStackTrace
的运行环境, 例如Nodejs.Error.captureStackTrace
的第一个参数是object
, 第二个可选参数是一个function
.Error.captureStackTrace
会捕获堆栈信息, 并在第一个参数中创建stack
属性来存储捕获到的堆栈信息. 如果提供了第二个参数, 该函数将作为堆栈调用的终点. 因此, 捕获到的堆栈信息将只显示该函数调用之前的信息.用下面的两个demo来解释一下. 第一个, 仅将捕获到的堆栈信息存于一个普通的对象之中:
从上面的示例可以看出, 首先调用函数
a
(被压入堆栈), 然后在a
里面调用函数b
(被压入堆栈且在a
之上), 然后在b
中捕获到当前的堆栈信息, 并将其存储到myObj
中. 所以, 在控制台输出的堆栈信息中仅包含了a
和b
的调用信息.现在, 我们给
Error.captureStackTrace
传递一个函数作为第二个参数, 看下输出信息:当将函数
b
作为第二个参数传给Error.captureStackTraceFunction
时, 输出的堆栈就只包含了函数b
调用之前的信息(尽管Error.captureStackTraceFunction
是在函数d
中调用的), 这也就是为什么只在控制台输出了a
. 这样处理方式的好处就是用来隐藏一些与用户无关的内部实现细节.参考
JavaScript Errors and Stack Traces in Depth
The text was updated successfully, but these errors were encountered: