For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/stage/4/proposal-error-cause.md.
  • 简体中文
  • Error Cause S4

    中文标题:错误原因

    提案概览
    提案速览

    该提案为 Error() 构造函数添加了一个 cause 选项,允许错误链带有原始原因的上下文信息。它解决了错误包装缺乏标准约定、难以诊断嵌套失败的问题。js 等主要环境中广泛实现。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    错误原因

    状态:第 4 阶段

    作者:@legendecas

    提案发起人:@legendecas, @hemanth

    错误链

    错误被构造用来表示运行时异常。为了帮助诊断意外行为,错误需要附加上下文信息,例如错误消息、错误实例属性,以解释当时发生了什么。

    如果错误是从深层内部方法抛出的,那么抛出的错误可能不容易在没有适当异常设计模式的情况下被轻松处理。捕获错误并附加额外的上下文信息抛出是一种常见的错误处理模式。有多个方法可以为捕获的错误附加额外的上下文信息:

    async function doJob() {
      const rawResource = await fetch('//domain/resource-a')
        .catch(err => {
          // 如何正确包装错误?
          // 1. throw new Error('下载原始资源失败:' + err.message);
          // 2. const wrapErr = new Error('下载原始资源失败');
          //    wrapErr.cause = err;
          //    throw wrapErr;
          // 3. class CustomError extends Error {
          //      constructor(msg, cause) {
          //        super(msg);
          //        this.cause = cause;
          //      }
          //    }
          //    throw new CustomError('下载原始资源失败', err);
        })
      const jobResult = doComputationalHeavyJob(rawResource);
      await fetch('//domain/upload', { method: 'POST', body: jobResult });
    }
    
    await doJob(); // => TypeError: 获取失败

    如果错误被链接并带有原因,那么它将对诊断意外异常大有帮助。如上面的例子所示,对于一个简单的错误处理场景,要附加上下文消息给捕获的错误,需要做相当多的重复工作。此外,对于哪个属性表示原因没有统一的共识,这使得开发工具无法揭示原因的上下文信息。

    提出的解决方案是向 Error() 构造函数添加一个额外的选项参数,其中包含 cause 属性,该属性的值将分配给错误实例作为属性。这样错误可以被链接起来,而无需在包装错误时进行不必要且过于繁琐的仪式。

    async function doJob() {
      const rawResource = await fetch('//domain/resource-a')
        .catch(err => {
          throw new Error('下载原始资源失败', { cause: err });
        });
      const jobResult = doComputationalHeavyJob(rawResource);
      await fetch('//domain/upload', { method: 'POST', body: jobResult })
        .catch(err => {
          throw new Error('上传作业结果失败', { cause: err });
        });
    }
    
    try {
      await doJob();
    } catch (e) {
      console.log(e);
      console.log('由以下原因引起', e.cause);
    }
    // 错误:上传作业结果失败
    // 由以下原因引起 TypeError: 获取失败

    兼容性

    在 Firefox 中,Error() 构造函数可以接收两个额外的可选位置参数:fileNamelineNumber。这些参数将被分配给新构造的错误实例,名称分别为 fileNamelineNumber

    然而,在 ECMAScript 或 Web 标准中都没有定义这种行为。由于本提案中的第二个参数必须是一个带有 cause 属性的对象,因此它将能与字符串区分开来。

    实现

    Polyfill:

    JavaScript 环境:

    • Chrome,发布于 93,
    • Firefox,发布于 91,
    • Safari,发布于 15,
    • Node.js,发布于 v16.9.0

    常见问题

    AggregateError 的区别

    这两者之间的关键区别在于 AggregateError 中的错误不一定相关。AggregateError 只是一堆碰巧被捕获并聚合在一个地方的错误,它们可能完全不相关。例如,jobAjobBPromise.allSettled([ jobA, jobB ]) 中可以彼此无关。然而,如果错误是从 jobA 的几层深处抛出的,cause 属性可以累积这些层级的信息以帮助理解到底发生了什么。

    使用 AggregateError,我们获得的是广度。使用 cause 属性,我们获得的是深度。

    为什么不使用自定义的 Error 子类

    虽然有很多方法可以达到本提案的行为,但如果 cause 属性由语言明确指定,调试工具可以可靠地使用此信息,而不是与开发者约定如何正确构造错误。