12k
All articles

深入解析 JavaScript 的 Error.isError()

Error.isError() 可跨 realm 判断真实的 JavaScript 错误,解释它为何优于 instanceof Error,并介绍安全回退用法。

OpenReplay Team
OpenReplay Team
深入解析 JavaScript 的 Error.isError()

Error.isError(value) 是一个静态方法,仅当 value 是真正的 Error 对象时才返回 true。由于它检查的是内部标记([[ErrorData]])而非遍历原型链,因此即使跨 realm(领域)也能保持可靠。

如果你曾经打开错误追踪工具,却在本该出现真实异常的地方看到一个空的 {},那你已经遇到过这个方法所要解决的问题了。在某个环节中,某次 instanceof 检查悄无声息地判定你的错误不是错误。该方法已在 ECMAScript 2026 中标准化,这意味着 instanceof Error 长期存在的缺陷(来自 iframe 的错误被判定为非错误,而伪造对象却被判定为错误)终于有了一流的解决方案。本文将讲解该方法的作用、它为何优于 instanceof、其背后的确切机制、边界情况,以及如何配合安全回退方案来采用它。

其行为完全符合预期:Error.isError(new Error())true,而 Error.isError({ message: 'x' })false,因为该方法验证的是对象是如何被构造的,而不仅仅是它继承自什么。

核心要点

  • Error.isError() 针对内部 [[ErrorData]] 槽执行带标记(branded)检查,与 Array.isArray() 所使用的不可伪造检查属于同一类别,因此用户态代码无法蒙混过关。
  • instanceof Error 会以两种相反的方式失效:对于在另一个 realm 中创建的真实错误返回 false,而对于原型被设置为 Error.prototype 的伪造对象返回 true
  • 该方法对 TypeError 等内置子类、正确 extend Error 的类,以及浏览器中的 DOMException 均返回 true,不过 Safari 目前对 DOMException 返回 false
  • Error.isError() 是 ECMAScript 2026 的一部分,已在 Chrome/Edge 134+、Firefox 138+、Node.js 24.0.0+ 以及 Safari 18.4(部分支持)中发布。
  • 请在边界处使用它(全局处理器、日志衔接代码、Worker、iframe、SSR/边缘计算),在这些场景中,instanceof 的一次静默漏判会让真实错误在日志里变成一个空对象。

为什么 instanceof Error 不够用?

instanceof Error 会以两种相反的方式失效,且两者都是静默发生的。TC39 提案阐明了第一种情况:一个跨越了 realm 边界的真实错误(无论来自 iframe 还是 Node 的 vm 模块)会返回假阴性结果。每个 realm 都有自己的 Error 构造函数,因此在 iframe 中创建的错误并不是你的 Error 的实例。

第二种失效恰好相反:任何原型链中包含 Error.prototype 的对象都能通过检查,而它并非真正的错误。以下代码展示了这两种失效:

// Failure 1 — cross-realm error reads as NOT an error
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const crossRealmError = new iframe.contentWindow.Error('from iframe');

crossRealmError instanceof Error;   // → false  (wrong)
Error.isError(crossRealmError);     // → true   (correct)

// Failure 2 — fake object reads as an error
const fake = { message: "I'm not real" };
Object.setPrototypeOf(fake, Error.prototype);

fake instanceof Error;              // → true   (wrong)
Error.isError(fake);                // → false  (correct)

这两个结果都是有文档规定的契约,而非偶然。MDN 对该方法的参考文档将其定位为 instanceof Error 的稳健替代方案,原因正是它规避了这两种失效模式:借用原型不足以通过检查,而在另一个 realm 中构建的错误仍能通过检查。instanceof 沿着原型链比较构造函数身份,因此在这两种情况下都会给出错误结论。

输入instanceof Error鸭子类型('message' in xError.isError()
跨 realm 的 Error(iframe/worker/vm)false⚠️ 视情况而定true
Object.setPrototypeOf(obj, Error.prototype)true⚠️ truefalse
class MyError extends Error 的实例truetruetrue

Error.isError() 的底层原理是什么?

在底层,Error.isError() 针对内部槽执行带标记检查,而不是检视原型链。MDN 直接描述了这一机制:该方法查找的是 Error() 构造函数在其所构建的每个错误上安装的私有字段。这与 Array.isArray() 背后的手法完全相同,也与 in 运算符测试属性的方式颇为相近。

Array.isArray() 这个类比正是应当牢记的思维模型。Array.isArray() 同样接受在不同 realm 中构建的数组,而在这种情况下 instanceof Array 会返回 false,因为每个 realm 持有各自独立的 Array 构造函数。Error.isError() 为错误对象带来了同样的、对 realm 安全的标记机制。

Stage 4 规范文本将该槽命名为 [[ErrorData]],并将 IsError 操作保持为三个步骤:任何非对象立即判定失败,任何带有该槽的值判定通过,其余一律判定失败。该槽在构造时设置,无法从 JavaScript 中伪造。

为什么用内部槽而不是 Object.prototype.toString?因为标签伪造已经攻破了这个老办法。提案作者将这个问题提交给了委员会:自从 Symbol.toStringTag 出现后,一项本来既可靠又无法伪造的检查,两方面都不再成立。而且由于除 Object#toString 之外没有任何东西会查询错误槽,用户代码根本无从获得可靠的检测手段。Error.isError() 恰好填补了这一空白。

值得了解的行为细节

Error.isError() 对整个 Error 家族返回 true,对其他一切返回 false,且不会抛出异常。MDN 的示例显示 new Error()new TypeError()new DOMException() 都返回 true,而不带参数调用,或传入 {}nullundefined17 或字符串 "Error" 时则返回 false。由于规范中的谓词对任何非对象以及缺少该槽的对象都返回 false,因此原始值和 null 都会被干净地处理,而不会引发异常。

正确扩展的自定义类能够被识别,因为它们继承了该标记:

class ValidationError extends Error {}
Error.isError(new ValidationError('bad input')); // → true

只有那些从未调用 Error 构造函数的仿冒对象才会被拒绝。DOMException 的情况有一个值得记住的细微之处。MDN 的规则是 DOMException 实例可以通过检查。DOMException 在形式上并不是 Error 的子类,因为其构造函数并不继承自 Error 构造函数,但它携带相同的标记,所以带标记的检查仍将其视为错误。Safari 是个例外:Chrome 在 Firefox 138 发布当月的综述中记录了 Safari 对 DOMException 返回 false,这也是为什么尽管所有主流引擎现已实现该方法,它仍未达到 Baseline 状态。出于同样的原因,MDN 仍将其标记为有限可用(limited availability)。请将这一个案例视为尚未统一的情况。

何时使用 Error.isError()

请在边界处使用 Error.isError()(全局错误处理器、日志与错误上报的衔接代码、测试运行器、库、SSR/边缘计算、Worker、iframe 以及浏览器扩展),在这些位置,instanceof 的一次静默漏判会让真实错误在日志里变成一个空的 {}。在作用域收窄、同一 realm 内的代码中,普通的 instanceof 完全够用;真正的收益体现在值跨越执行上下文的边缘地带。

这对应着一种真实的上报失效模式:边界处的 instanceof 检查将一个真正抛出的错误重新归类为普通对象,于是它进入你的数据管道时既没有消息也没有堆栈。会话回放(Session replay)在此是一项有用的技术:回放会话可以呈现出实际抛出的控制台错误,从而暴露浏览器所见与你的衔接代码所上报内容之间的差距。解决办法是在这些边界处、在任何内容被序列化或记录之前,使用 Error.isError() 进行标记检查。

浏览器与运行时支持,以及安全回退方案

Error.isError() 是 ECMAScript 2026(第 17 版)的一部分,Ecma International 于 2026 年 6 月 30 日批准了该版本;该提案在 2025 年 5 月的 TC39 会议上进入 Stage 4。在浏览器中,它从 Chrome 和 Edge 134、Safari 18.4 以及 2025 年 4 月 29 日发布的 Firefox 138 开始可用。在服务端,Node.js 24.0.0 通过升级到 V8 13.6 引入了该特性,同期落地的还有 Float16Array、显式资源管理、RegExp.escape 以及 WebAssembly Memory64。

若想实现可直接替换、并在旧环境上优雅降级的方案,可进行特性检测:

function isError(value) {
  return typeof Error.isError === 'function'
    ? Error.isError(value)      // realm-safe on modern engines
    : value instanceof Error;   // fallback, not realm-safe
}

在 TypeScript 中,Error.isError(e) 同时充当类型守卫(type guard),可在 if 分支内将捕获到的 unknown 值收窄为 Error,因此无需手动断言即可类型安全地访问 e.message

结论

Error.isError() 弥补了鸭子类型和 instanceof 永远无法填补的空白:它询问的是引擎是否真的将某个值标记为错误,因此跨 realm 的错误和原型伪造的仿冒对象都能得到正确的判定。今天就把你的边界检查(日志衔接代码、全局处理器、Worker 与 iframe 的接缝处)切换到带特性检测的封装函数上,只在代码从不离开自身 realm 的地方保留 instanceof

常见问题

Error.isError() 是已经标准化,还是仍处于实验性提案阶段?

Error.isError() 已完全标准化。它在 2025 年 5 月的第 108 次会议上推进到了 TC39 流程的 Stage 4,并被纳入 ECMAScript 2026,即该语言规范的第 17 版。它不再是提案或实验性特性,因此那些称其“尚未标准化”或处于“Stage 3”的说法已经过时。请将其视为已正式发布的语言特性。

Error.isError() 对自定义错误类有效吗?

有效,前提是该类正确地扩展了 Error。定义为 'class MyError extends Error {}' 的类会继承 Error 构造函数设置的内部标记,因此 Error.isError(new MyError()) 返回 true。只有那些从未调用 Error 构造函数的仿冒对象会被拒绝,例如一个被强行将 Error.prototype 塞入原型链的普通对象。关键要求是正确的子类化,而不是类的名称。

Error.isError() 在 Safari 中可用吗?

Safari 18.4 及更高版本对常规 Error 对象支持 Error.isError(),但支持是部分性的。Safari 目前对 DOMException 实例返回 false,而规范和其他引擎返回 true。由于这一差距,MDN 未将该方法归类为 Baseline,web.dev 也标注其尚未实现统一可用。如果你的代码面向 Safari,请对 DOMException 的情况做防御性处理。

Error.isError() 比 instanceof Error 更快吗?

两者实际上都是常数时间的检查,因此性能并不是切换的理由。instanceof 会遍历原型链,而 Error.isError() 只读取单个内部标记,但实际差异可以忽略不计。真正的优势在于正确性:对于跨 realm 的错误和原型伪造的仿冒对象,Error.isError() 会给出正确答案,而 instanceof 在这些情况下会静默失效。选择它是为了在执行上下文边界上获得可靠性,而不是为了速度。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.