堆栈跟踪 API

V8 中抛出的所有内部错误在创建时都会捕获堆栈跟踪。可以通过非标准的 error.stack 属性从 JavaScript 访问此堆栈跟踪。V8 还具有各种钩子,用于控制如何收集和格式化堆栈跟踪,以及允许自定义错误也收集堆栈跟踪。本文档概述了 V8 的 JavaScript 堆栈跟踪 API。

基本堆栈跟踪 #

默认情况下,V8 抛出的几乎所有错误都具有一个 stack 属性,该属性保存最顶部的 10 个堆栈帧,格式化为字符串。以下是一个完全格式化的堆栈跟踪示例

ReferenceError: FAIL is not defined
   at Constraint.execute (deltablue.js:525:2)
   at Constraint.recalculate (deltablue.js:424:21)
   at Planner.addPropagate (deltablue.js:701:6)
   at Constraint.satisfy (deltablue.js:184:15)
   at Planner.incrementalAdd (deltablue.js:591:21)
   at Constraint.addConstraint (deltablue.js:162:10)
   at Constraint.BinaryConstraint (deltablue.js:346:7)
   at Constraint.EqualityConstraint (deltablue.js:515:38)
   at chainTest (deltablue.js:807:6)
   at deltaBlue (deltablue.js:879:2)

堆栈跟踪是在创建错误时收集的,无论错误在何处或抛出多少次,它都是相同的。我们收集 10 个帧,因为这通常足以有用,但不会太多以至于对性能产生明显的负面影响。可以通过设置变量来控制收集多少个堆栈帧

Error.stackTraceLimit

将其设置为 0 将禁用堆栈跟踪收集。任何有限的整数值都可以用作要收集的帧的最大数量。将其设置为 Infinity 表示将收集所有帧。此变量仅影响当前上下文;必须为每个需要不同值的上下文显式设置它。(请注意,在 V8 术语中,所谓的“上下文”对应于 Google Chrome 中的页面或 <iframe>)。要设置影响所有上下文的不同默认值,请使用以下 V8 命令行标志

--stack-trace-limit <value>

要在运行 Google Chrome 时将此标志传递给 V8,请使用

--js-flags='--stack-trace-limit <value>'

异步堆栈跟踪 #

--async-stack-traces 标志(从 V8 v7.3 开始默认启用)启用了新的 零成本异步堆栈跟踪,它使用异步堆栈帧丰富了 Error 实例的 stack 属性,即代码中的 await 位置。这些异步帧在 stack 字符串中用 async 标记

ReferenceError: FAIL is not defined
    at bar (<anonymous>)
    at async foo (<anonymous>)

在撰写本文时,此功能仅限于 await 位置、Promise.all()Promise.any(),因为对于这些情况,引擎可以在没有任何额外开销的情况下重建必要的信息(这就是为什么它是零成本的)。

自定义异常的堆栈跟踪收集 #

用于内置错误的堆栈跟踪机制是使用通用的堆栈跟踪收集 API 实现的,该 API 也可用于用户脚本。该函数

Error.captureStackTrace(error, constructorOpt)

在给定的 error 对象中添加一个堆栈属性,该属性在调用 captureStackTrace 时产生堆栈跟踪。通过 Error.captureStackTrace 收集的堆栈跟踪会立即收集、格式化并附加到给定的 error 对象。

可选的 constructorOpt 参数允许您传入一个函数值。在收集堆栈跟踪时,所有高于对该函数的最高调用(包括该调用)的帧都将从堆栈跟踪中排除。这对于隐藏对用户无用的实现细节很有用。定义捕获堆栈跟踪的自定义错误的通常方法是

function MyError() {
Error.captureStackTrace(this, MyError);
// Any other initialization goes here.
}

将 MyError 作为第二个参数传入意味着对 MyError 的构造函数调用不会显示在堆栈跟踪中。

自定义堆栈跟踪 #

与 Java 不同,Java 中异常的堆栈跟踪是一个结构化值,允许检查堆栈状态,V8 中的堆栈属性只保存一个包含格式化堆栈跟踪的扁平字符串。这仅仅是为了与其他浏览器兼容。但是,这不是硬编码的,而只是默认行为,可以由用户脚本覆盖。

为了效率,堆栈跟踪不会在捕获时格式化,而是在需要时格式化,即第一次访问堆栈属性时。堆栈跟踪是通过调用

Error.prepareStackTrace(error, structuredStackTrace)

并使用此调用返回的任何内容作为 stack 属性的值来格式化的。如果您将不同的函数值分配给 Error.prepareStackTrace,则该函数将用于格式化堆栈跟踪。它将传递正在为其准备堆栈跟踪的错误对象,以及堆栈的结构化表示。用户堆栈跟踪格式化程序可以自由地以他们想要的任何方式格式化堆栈跟踪,甚至返回非字符串值。在调用 prepareStackTrace 完成后保留对结构化堆栈跟踪对象的引用是安全的,因此它也是一个有效的返回值。请注意,自定义 prepareStackTrace 函数仅在访问 Error 对象的堆栈属性后才会被调用。

结构化堆栈跟踪是一个 CallSite 对象数组,每个对象代表一个堆栈帧。CallSite 对象定义了以下方法

默认堆栈跟踪是使用 CallSite API 创建的,因此通过该 API 可用的任何信息也可以通过此 API 获得。

为了维护对严格模式函数施加的限制,具有严格模式函数的帧及其下方的所有帧(其调用者等)不允许访问其接收器和函数对象。对于这些帧,getFunction()getThis() 返回 undefined

兼容性 #

此处描述的 API 特定于 V8,不受任何其他 JavaScript 实现支持。大多数实现确实提供了一个 error.stack 属性,但堆栈跟踪的格式可能与此处描述的格式不同。建议使用此 API 的方法是

附录:堆栈跟踪格式 #

V8 使用的默认堆栈跟踪格式可以为每个堆栈帧提供以下信息

这些信息中的任何一个都可能不可用,并且根据可用信息的多少,堆栈帧使用不同的格式。如果所有上述信息都可用,则格式化的堆栈帧如下所示

at Type.functionName [as methodName] (location)

或者,在构造调用情况下

at new functionName (location)

或者,在异步调用情况下

at async functionName (location)

如果只有 functionNamemethodName 之一可用,或者如果它们都可用但相同,则格式为

at Type.name (location)

如果两者都不可用,则使用 <anonymous> 作为名称。

Type 值是存储在 this 的构造函数字段中的函数的名称。在 V8 中,所有构造函数调用都将此属性设置为构造函数,因此除非在创建对象后主动更改了此字段,否则它将保存创建它的函数的名称。如果它不可用,则使用对象的 [[Class]] 属性。

一个特例是全局对象,其中不显示 Type。在这种情况下,堆栈帧的格式为

at functionName [as methodName] (location)

位置本身有几种可能的格式。最常见的是定义当前函数的脚本中的文件名、行号和列号

fileName:lineNumber:columnNumber

如果当前函数是使用 eval 创建的,则格式为

eval at position

…其中 position 是调用 eval 的完整位置。请注意,这意味着如果存在对 eval 的嵌套调用,则位置可以嵌套,例如

eval at Foo.a (eval at Bar.z (myscript.js:10:3))

如果堆栈帧位于 V8 的库中,则位置为

native

…如果不可用,则为

unknown location