编程语言研究书库

11 错误的类型与代价:规范没说的事,才最要紧

11 错误的类型与代价:规范没说的事,才最要紧

这一章讲什么

前六章把值和执行的规矩搭完了,这一章和下一章处理「搭好的东西坏了怎么办」。 而开宗明义的第一件事就反直觉:规范对错误几乎什么都没说。 Error 按规范只是个带 message 和 name 的对象;你可以 throw 任何值, 包括字符串、数字、null 甚至 undefined;你天天看的 stack trace, 规范里一个字都没有1。你实际打交道的错误,是「引擎和宿主拿各自的约定 把规范的空洞填上」的产物——而这个「填」的过程,正是全书三层框架的最后一次应用。

顶层全景:错误的双重身份与四层补丁

  一个错误同时服务两个目的:
    应用(恢复):  只需知道「哪类失败」→ 重试?降级?放弃?
    开发者(调试): 需要完整现场 → 哪行代码、怎么走到这的

  规范给的家底:Error{name, message},九个原生构造器
     │ 不够用,于是各层补:
     ▼
  引擎层:   stack trace(不在规范里!)+ 惰性序列化 + Error.stackTraceLimit
  Web 层:   DOMException —— 按「操作失败」命名的平行错误体系
  Node 层:  error code(ENOENT/ERR_*)——code 给机器、message 给人
  语言演进: ES2021 AggregateError / ES2022 cause / ES2024 SuppressedError

  图说:每一层补丁都对应一个「家底不够用」的具体场景;读懂补丁,就读懂了需求。

核心原理

3.1 大写 E 与小写 e:Error 对象 vs 被抛的任何值

先分清规范都搅在一起的两个概念:大写 E 的 Error 是对象; 小写 e 的「error」是任何被抛出的值2。而 throw 这个动作的本质是 打断正常执行,把一个值沿调用栈往上送,喊「我进行不下去了,别人来处理」3。

由于语言允许 throw "just a string"、throw 42、throw null、 throw undefined4,catch 到的值不能假设它有 .message 或 .stack—— 对一个字符串读 .message 是 undefined,对 null 读会直接抛错5。 早年不少流行库真就抛字符串;后来生态形成共识——「永远抛 Error 对象」, 理由朴素:抛任意值确实没什么用6。这一转变是约定的胜利,不是规范的胜利。

3.2 原生九个:七个结构相同的摆设

规范定义了九个原生 Error 构造器,常用的七个:Error(基类)、 SyntaxError、ReferenceError、TypeError、RangeError、URIError(涉及网址编码的错误)、 EvalError(已废弃,现代实现从不抛出)7。

关键事实书里说得毫不客气:前七个结构上完全一样,只差 name 一个字段。 对恢复逻辑而言,TypeError 还是 RangeError 给不出任何 「重试 / 降级 / 中止」的建议——两者都只是「你代码写错了」; 对调试而言,知道异常类型也定位不到 bug8。应用和开发者真正需要的东西 ——给调试的 stack trace、给恢复的 error code——都是引擎和平台的 非标准补充9。这一节是全书对「规范最小主义」最辛辣的实证: 错误的类型体系看起来齐全,其实空心。

3.3 现代补丁三连:AggregateError、cause、SuppressedError

ES2021–2024 的修补方向很一致:别再发明更多没什么用的类型区分, 去保住「调试和恢复真正需要的信息」10。

AggregateError(ES2021):一个错装多个错。为 Promise.any() 而生—— 它并行(多个任务同时推进)尝试多个异步操作,全灭时要把每个失败都报告出来, 而 try-catch 一次只能接一个;AggregateError 用 errors 数组 把多个错误打包成一个11。它的 errors 属性同时服务两个目的: 恢复侧查「哪个具体操作挂了」,调试侧拿到多份栈12。

cause(ES2022):给所有原生 Error 加的可选属性,解决系统边界上的 「两种观众」问题:往外抛的错误要干净(不带内部密钥和用户数据), 但调试要完整的失败链。cause 让你用一个对外安全的错误包住内部错误, 完整现场跟着链条传下去13。书里给的例子是支付处理器: throw new Error('Failed to authorizePayment', { cause: {...内部细节} })14。

SuppressedError(ES2024):清理代码(收尾的释放动作)本身也失败时, 原来的错和新错都别丢——.error 存原错,.suppressed 存清理错。 典型场景是新 using 声明的自动收尾清理:作用域退出时清理函数抛错, 而错误正在传播中,两个都得活15。

三个补丁合起来,书里点破了语言设计观的转向: 承认错误有双重目的,让错误同时携带两套信息16。

3.4 DOMException:隔壁体系的正面教材

Web 标准(W3C)当年造 DOM 规范时,发现 JavaScript 的原生错误类型 表达不了浏览器 API 想报的失败,于是另起炉灶做了 DOMException—— 一套按应用操作命名的平行错误体系:NetworkError、SecurityError、 NotAllowedError、QuotaExceededError、AbortError、TimeoutError、 InvalidStateError、DataCloneError……还保留一个遗留的数字 code17。

它和原生体系的对比是全书最锋利的一组判断:DOMException 的名字 能指导恢复(NetworkError → 重试;QuotaExceededError → 清理缓存; NotAllowedError → 引导授权),因为它们描述「应用里什么失败了」; 而原生 TypeError/RangeError 描述「哪条语言规则被违反了」, 对恢复零指导18。调试侧两边倒是一样惨:没有 stack trace 都没用19。 DOMException 是今天最常遇到的错误体系之一,使用广度仅次于 Error 和 TypeError20。

3.5 Node.js 的错误码:一个句点搞崩 npm 的教训

Node.js 一开始对错误没做特殊处理:抛原生类型,信息全塞 message。 直到维护者(作者本人)发现一个恐怖的事实:开发者开始逐字符匹配错误消息 写恢复逻辑——if (error.message === 'The foo argument should be a string')21。 后果:消息改一个字,应用就崩。作者亲历:他给 Node.js 的一条错误消息 末尾加了一个句点,就把 npm 包管理器搞坏了——给人看的调试文本, 被当成了机器读的恢复协议22。

解法是从 DOMException 借来的:每个错误带 code 属性给机器, message 给人。系统错误用 ENOENT(文件不存在)、EACCES(权限不足)、 EMFILE(文件打开太多)这类约定名;断言错误 ERR_ASSERTION; 参数类型错误 ERR_INVALID_ARG_TYPE……按 code 写分支: ENOENT 就创建文件、EACCES 就中止、EMFILE 就稍后重试23。

规则从此分明:code 决定恢复策略,message 供人调试; 消息终于可以自由演化,而依赖恢复逻辑的应用改去依赖 「几乎不变的机器可读标识符」24。这是「接口要分机器层和人层」的 一次教科书级操作——教训是用生产事故换来的。

3.6 调用栈:不在规范里的贵重品

stack trace(调用栈)捕获的是 Error 创建那一刻的执行链: 哪个函数、哪一行。书里的定位:只为调试,永不恢复—— 应用该不该重试,永远不需要知道「错发生在 database.js 第 42 行」25。

而它的出身和成本都配得上「贵重品」三个字:

  • 出身:规范对它只字未提,是每个引擎各自发明的调试工具26;
  • 存储:不是字符串,是 Error 对象里的一堆结构化帧对象(函数名+源码位置), 不管你读不读 .stack 都在那里;第一次读 .stack 时才序列化成字符串, 读完帧对象释放27;
  • 成本:单个 Error 可以贵到几千字节;一个每秒处理数千错误的高吞吐(单位时间处理量大的)系统, 调试信息能吃掉数 MB 内存——哪怕错误马上被丢弃,抓取现场的钱已经付了28。

顺带破一个流传甚广的迷思:「用 try-catch/throw 会让 V8 不去优化你的代码」 ——曾经是,现在不是。V8 优化带 try-catch 的函数毫无压力; 真正的陷阱在别处:优化器(引擎里负责提速的部件)只能优化实际运行过的代码。一个函数被调用几千次、 catch 块一次都没执行过,它的优化版本里根本不含 catch 块; 哪天突然来了个会触发 throw 的输入,V8 被迫去优化——不是 throw 的错, 是它突然跑进了从没跑过的分支29。

3.7 降本两招:惰性序列化 + 限深度

引擎和开发者各有一招降本:

  1. 惰性序列化:内部帧到字符串的转换推迟到你第一次访问 .stack; 从不访问,就不花这个钱30。
  2. Error.stackTraceLimit(V8 的非标准属性):控制抓几帧。 设成 5 只留 5 帧;设成 0 一帧不抓31。

书里的经验法则跟着结论:抓的调试信息越多,建 Error 越慢、内存越高; 而这些信息对恢复毫无帮助32。生产系统下调栈深度,是性价比很高的优化—— 代价是事故排查时现场变薄,这个权衡属于「两个目的对立」的又一个实例。

3.1b 双重目的的完整论证:两个错误对象对照

7.1 节的论证值得用原书的对照实验完整重述一遍,因为它是全章的题眼33。

应用那侧,只需要三个字段就够做决策:

const error = {
  type: 'NETWORK_ERROR',
  retryable: true,
  fallbackAvailable: true
};

「网络失败了、可以重试、有降级方案」——恢复策略当场就能定34。

开发者那侧,同样一次失败要这些才够定位:

const error = {
  message: 'Connection to database timed out after 5000ms',
  stack: 'at connectDB (db.js:42:15)\n at initialize (app.js:12:8)',
  timestamp: '2024-01-15T10:32:45.234Z',
  context: { userId: 1234, operation: 'user_fetch' }
};

原因、位置、时间、受影响的背景信息,全是给人看的35。

书里随即点破这组对照的不对称:应用要的那点信息,收集起来很便宜; 开发者要的现场,收集起来昂贵得多——而语言只给了 Error{name, message} 这点家底,两边都不够,于是引擎、运行时、应用各自想办法补齐, 每种补齐都有自己的价钱36。这句「都有价钱」是 7.4 节栈成本论证的引子, 也是「错误的双重目的」这个框架最锋利的地方: 增加任何调试信息,都在向恢复路径收税。

3.2b 大写 E 与小写 e:catch 的防御姿势

「可以 throw 任何东西」的直接后果,catch 块不能假设拿到的是 Error:

try {
  someFunction();
} catch (e) {
  console.log(e.message);   // 可能是 undefined
  console.log(e.stack);     // 可能是 undefined
  console.log(e.toString()); // 对 null/undefined 会直接抛!
}

对字符串抛错,.message 是 undefined;对 null 抛错, e.toString() 会再抛一个 TypeError——异常处理代码自己变成异常源37。 现代生态的公约数是「永远抛 Error 对象」,理由朴素:抛任意值确实没什么用, Error 至少带着 name、message,外加工 引擎附送的 stack38。 但读别人的代码(尤其是老库)时,这个防御姿势不能省: 先确认是对象、再读属性,或者干脆 e instanceof Error 分流39。

3.3b 现代补丁三连的代码级细节

AggregateError 的完整用法(ES2021,为 Promise.any() 而生)40:

const promises = [
  fetch('/api/primary'),
  fetch('/api/backup1'),
  fetch('/api/backup2')
];
try {
  const result = await Promise.any(promises);  // 并行请求,先用先赢
} catch (e) {
  console.log(e.errors[0]);  // 三个全灭时:e 是 AggregateError
  console.log(e.errors[1]);
  console.log(e.errors[2]);
}

errors 数组是它区别于老七样的唯一结构差异,也是它同时服务两个目的 的载体:恢复侧逐个查「哪个操作挂了」,调试侧一次拿到三份栈41。 除 Promise.any() 之外的使用「可能但罕见」42。

SuppressedError 的完整用法(ES2024,收尾清理的保险丝)43:

class DatabaseConnection {
  [Symbol.dispose]() {
    throw new Error('Failed to close connection');  // 清理本身也炸了
  }
}
try {
  using connection = new DatabaseConnection();
  throw new Error('Things went badly');   // 先抛的错
} catch (err) {
  console.log(err.error);      // 'Error: Things went badly'      —— 原错
  console.log(err.suppressed); // 'Error: Failed to close connection' —— 被压下的
}

using 声明在离开作用域时自动调 Symbol.dispose;若清理又抛, 两个错误都保在 .error 与 .suppressed 里—— 没有这个容器,原来的错和清理的错总得死一个44。

cause 的包法(ES2022,边界处的标准动作)45:

function processPayment(...args) {
  try {
    return thirdParty.authorizePayment(...args);
  } catch (paymentError) {
    throw new Error('Failed to authorizePayment', {
      cause: { name: paymentError.name, message: paymentError.message }
    });
  }
}
try {
  processPayment(...args);
} catch (err) {
  console.log(err.cause);   // 内部细节在链条上,不外泄给外层展示
}

「cause 里能不能放非 Error 的值」有争论,书里定性为「学究之争」—— 要紧的是它给了标准做法:边界上抛干净的、链条上保完整的46。

3.4b DOMException 的全表与「为什么它更像对的答案」

DOMException 的常用类型,原书给了八行表,照录47:

类型含义
NetworkError网络操作失败
SecurityError操作违反安全策略
NotAllowedError用户拒绝了权限
QuotaExceededError存储配额超限
AbortError操作被中止
TimeoutError操作超时
InvalidStateError对象处于错误状态
DataCloneError值无法被克隆

注意这张表的命名角度:每一行都是一次「应用层的操作失败」, 而不是一条「语言规则违反」——QuotaExceededError 告诉你 「清缓存或提示用户」,NetworkError 告诉你「重试或降级」, 这就是书里「DOMException 的名字能指导恢复」判断的依据48。 对照原生七样的 TypeError/RangeError:它们说的是 「你代码写错了」,对恢复策略零指导——「重试?你的代码还是错的。」49 调试侧两边打平:没有 stack,谁都帮不上50。 作者补了一句使用广度:DOMException 是开发者最常遇到的错误体系, 仅次于 Error 和 TypeError51。

3.5b 错误码体系的全景

Node.js 借 DOMException 思路建立的码体系,书里分了类52:

  • 系统错误:ENOENT、EACCES、EMFILE……来自操作系统, 大写字母开头,是 Node 里最古老的一批;
  • 断言错误:ERR_ASSERTION,expectation 被违反;
  • 类型/参数错误:ERR_INVALID_ARG_TYPE 等,Node 自己校验参数时抛。

switch 分支的样板代码原书给了,值得照抄骨架53:

try {
  await someOperation();
} catch (e) {
  switch (e.code) {
    case 'ENOENT':        // 文件不存在——也许该创建
      break;
    case 'EACCES':        // 权限不足——致命,往上抛
      throw e;
    case 'EMFILE':        // 打开文件太多——稍后重试
      await delay(100);
      return retry();
  }
}

「码给机器、消息给人」的分工确立之后,消息终于可以自由演化 (改措辞、加细节、补建议)而不怕砸烂依赖恢复逻辑的应用—— 因为应用依赖的是「几乎不变的机器可读标识符」54。 再对比前节的教训:当年一个句点就能搞崩 npm,因为恢复逻辑绑在消息上; 如今绑在码上,消息随便改55。从「匹配文本」到「匹配码」, 是一次接口设计的范式迁移,代价是一整个生态的兼容债。

3.6b 调用栈的成本解剖:两笔开销、两个对策

栈的成本拆成两笔,书里分得很清56:

第一笔:抓取。 Error 创建的那一刻,引擎要回溯「我是怎么走到这的」, 逐帧提取函数名和源码位置——每一步都是好几次内存访问, 单个看都贵57。这笔钱的残酷在于不可分摊: 每个 Error 各自记各自的现场,帧信息在 Error 之间不共享58。

第二笔:序列化。 第一次访问 .stack 时, 存的帧对象被格式化成字符串然后释放——要访问 V8 的源码信息, 这步不便宜;从不访问,这步就不发生,但帧对象照样占着内存59。

两个对策也因此各管一笔:惰性序列化(第一访问才格式化)管第二笔; Error.stackTraceLimit(V8 非标准属性)管第一笔——设 5 帧只留 5 帧, 设 0 一帧不抓60。经验法则跟着来:抓的调试信息越多, 建 Error 越慢、内存越高;而这对恢复毫无帮助61。 「恢复与调试两个目的的对立」,在这里是字面意义的成本对立。

至于「try-catch 让 V8 不去优化」的老迷思,书里专门辟了一段辟谣: 曾经是真的,现在不是——V8 优化带 try-catch 的函数毫无压力; 真正的陷阱是优化器只优化跑过的代码:一个被调用几千次、 catch 块一次没触发过的函数,它的优化版本里根本没有 catch 块; 某天突然来了个会触发 throw 的输入,被迫去优化—— 不是 throw 的错,是突然跑进了从没跑过的分支62。

3.0b 章首坐标:原书给这一章立的五个问题

第 7 章开篇的「本章覆盖」清单,其实是五个层层递进的问题, 照录下来当本章的导览51:

  1. 错误如何同时服务「恢复」与「调试」两种需要? —— 7.1 节,双重目的;
  2. 为什么调用栈既珍贵又昂贵? —— 7.4 节,栈的成本与对策;
  3. 错误处理器该放在哪? —— 这是 12 章 3.2 节的内容,但框架在本章立起: 「语义边界」四个字在 7.1 的双重目的论证里就已埋下;
  4. 异步操作为什么会打断错误传播? —— 12 章 3.5 节的断层图;
  5. 全局错误处理器什么时候是最后一根稻草? —— 12 章 3.6 节的兜底体系。

五个问题里,前两个由本章(11)回答,后三个由下一章(12)回答; 而贯穿五个问题的主线只有一条:恢复要背景信息,调试要细节现场, 收集现场要钱——每一次设计决策(建不建栈、放不放 cause、 写不写 code)都是这三个量的权衡。 读这一章时带着这条主线, 十条总结就不是散点,而是一棵树的枝叶。

3.6c 第 7 章与全书的缝合:错误的三层归属

读到这里,全书的三层框架可以最后一次兑现:错误体系里, 每一层都在补上一层的空缺,而且补法各有性格52:

  • 语言层只给了 Error{name, message} 和「随便 throw」的宽门框, 留下的空洞最大:没有恢复语义、没有调用栈、没有错误链;
  • 引擎层补的是「开发者侧」:V8 自己发明了 stack trace (规范里没有!),并给它做了惰性序列化——调试体验是引擎送的,不是语言给的;
  • 宿主与生态层补的是「应用侧」:W3C 造了 DOMException 的语义命名, Node.js 造了机器可读的错误码,ES2021/2022/2024 的 AggregateError/cause/SuppressedError 则是语言层迟到的大补丁53。

把这个归属记牢,遇到任何错误相关的「为什么」都有了排查路线: 栈不对找引擎版本,码不对找 Node 文档,类型语义不对找 Web 标准, 而「能不能 throw 这个值」——语言层从一开始就说:随便54。

这也解释了为什么错误一章放在对象章之后:错误对象本身就是普通对象 (可以挂任意属性、走原型链),引擎和宿主只是往这个普通对象上 多钉了些钉子(message、stack、code、cause、errors)。 理解了对象,错误体系就没有新东西;不理解对象,错误体系处处是魔法。

作者的判断与证据

有证据的判断:

  • 「前七个原生 Error 结构相同」「stack trace 不在规范里」都是可查证事实63;
  • 「一个句点搞崩 npm」是作者亲历的一手工程史,可信度极高(他是当事维护者)64。

作者的主张:

  • 「原生类型区分无用、DOMException 命名才有恢复价值」是锐利的判断, 但「调试侧两者一样没用」略显绝对——类型信息在过滤日志、聚合告警时仍有价值。
  • 「下调 stackTraceLimit」是实践建议,代价(排查现场变薄)需要团队自己权衡。

判断(我们的,不是书里的): 本章最重要的洞察是「错误类型命名学」的对照: 原生错误按「违反了哪条语言规则」命名,对恢复毫无指导;DOMException/错误码按 「应用发生了什么」命名,天然服务恢复逻辑。这个对照可以照搬到任何语言/框架的 错误体系设计上——先问「读者是机器还是人,他要做什么决策」。 如果错,会错在: 如果团队把全部精力投入错误分类学而不解决「错误有没有被 送到能处理它的地方」(12 章的边界问题),分类再漂亮也是屠龙之技——两章要合读。

边界与局限

  • SuppressedError/using 属 ES2024 新特性,书中也承认实践中极少用到65。
  • Error.stackTraceLimit 是 V8 私有属性,别的引擎不保证; 浏览器环境下调栈深度的替代方案书中没讲。
  • 错误监控体系(Sentry 类)如何把堆传上服务器、序列化格式,全在范围外。
  • 「几千字节一个 Error」没给测量条件(帧数、引擎版本),只能当量级参考66。

可带走的

  1. 规范只管 Error{name,message};stack trace、错误码都是引擎/宿主私货。
  2. throw 可以抛任何东西;catch 到的值先确认是不是 Error 再读属性。
  3. 原生七个类型只差名字,不给恢复建议;别再发明同类新类型。
  4. DOMException 按「什么操作失败了」命名,才有恢复指导价值。
  5. 错误码的教训:机器匹配 code,人读 message;消息演化,码不变。
  6. cause 用来在边界包错误:对外干净,对内保完整链。
  7. 一个 Error 几千字节;恢复路径上的错误创建是真实成本。
  8. try-catch 本身不再拖慢优化;去优化源于「突然跑进没跑过的分支」。
  9. 调 Error.stackTraceLimit 可以省钱;栈只为调试,永不服务恢复。

原文地图

主题原书章原文位置
「约定大于规范」总论7 Errorstext/26-ch07-7-errors.txt:47(搜「convention than by specification」)
恢复 vs 调试双重目的同上text/26-ch07-7-errors.txt:77(搜「Recovery strategy」) · :80(搜「full context」)
throw 的本质与任意值同上text/26-ch07-7-errors.txt:132(搜「interrupt normal code execution」) · :140(搜「just a string」)
生态统一到 Error 对象同上text/26-ch07-7-errors.txt:185(搜「standardized on always throwing」)
原生九类型与「结构相同」同上text/26-ch07-7-errors.txt:197(搜「nine built-in」) · :232(搜「structurally identical」)
AggregateError同上text/26-ch07-7-errors.txt:256(搜「Promise.any()」)
SuppressedError 与 using同上text/26-ch07-7-errors.txt:307(搜「ES2024」)
DOMException 平行体系同上text/26-ch07-7-errors.txt:396(搜「parallel error system」)
「能指导恢复」的对比同上text/26-ch07-7-errors.txt:504(搜「guide retry logic」)
cause 属性同上text/26-ch07-7-errors.txt:522(搜「cause property」)
设计观转向同上text/26-ch07-7-errors.txt:570(搜「shift in how JavaScript approaches」)
逐字符匹配消息同上text/26-ch07-7-errors.txt:607(搜「matched the Error message exactly」)
「一个句点搞崩 npm」同上text/26-ch07-7-errors.txt:622(搜「adding a period」)
错误码体系同上text/26-ch07-7-errors.txt:634(搜「code property for machines」) · :645(搜「ENOENT」)
code 与 message 分工同上text/26-ch07-7-errors.txt:679(搜「for machines to determine recovery」)
栈的规范缺席与用途同上text/26-ch07-7-errors.txt:697(搜「does not mention stack traces at all」)
帧对象与惰性序列化同上text/26-ch07-7-errors.txt:761(搜「structured collection of frame objects」) · :861(搜「Lazy serialization」)
几千字节的成本同上text/26-ch07-7-errors.txt:813(搜「couple thousand bytes」)
stackTraceLimit同上text/26-ch07-7-errors.txt:864(搜「stackTraceLimit」)
try-catch 去优化迷思同上text/26-ch07-7-errors.txt:774(搜「prevailing wisdom」)
「信息越多越贵」法则同上text/26-ch07-7-errors.txt:885(搜「the more debugging information」)
双重目的的两个错误对象同上text/26-ch07-7-errors.txt:93(搜「NETWORK_ERROR」) · :110(搜「Connection to database timed out」)
AggregateError / SuppressedError / cause同上text/26-ch07-7-errors.txt:270(搜「Promise.any(promises)」) · :320(搜「DatabaseConnection」) · :535(搜「processPayment」)
DOMException 八类型与「能指导恢复」同上text/26-ch07-7-errors.txt:444(搜「NetworkError」) · :504(搜「guide retry logic」)
错误码分类与分工同上text/26-ch07-7-errors.txt:668(搜「System errors」) · :679(搜「static, machine-readable」)
栈的两笔开销与两个对策同上text/26-ch07-7-errors.txt:819(搜「two places」) · :864(搜「stackTraceLimit」)
章首五问同上text/26-ch07-7-errors.txt:16(搜「How errors serve recovery」)

Footnotes

  1. 出处:「7 Errors」第 47 段(text/26-ch07-7-errors.txt:47,搜「convention than by specification」)。 ↩

  2. 出处:「7 Errors」第 132 段(text/26-ch07-7-errors.txt:132,搜「capital-E Errors」)。 ↩

  3. 出处:「7 Errors」第 132 段(text/26-ch07-7-errors.txt:132,搜「interrupt normal code execution」)。 ↩

  4. 出处:「7 Errors」第 139–148 段(text/26-ch07-7-errors.txt:140,搜「just a string」)。 ↩

  5. 出处:「7 Errors」第 158–175 段(text/26-ch07-7-errors.txt:173,搜「Might be undefined or even throw」)。 ↩

  6. 出处:「7 Errors」第 185 段(text/26-ch07-7-errors.txt:185,搜「standardized on always throwing Error objects」)。 ↩

  7. 出处:「7 Errors」第 197–227 段(text/26-ch07-7-errors.txt:197,搜「nine built-in」;EvalError 的「Deprecated, never thrown」见第 221 段)。 ↩

  8. 出处:「7 Errors」第 232 段(text/26-ch07-7-errors.txt:232,搜「structurally identical」)。「TypeError 还是 ReferenceError 与重试/降级/中止无关」在同段。 ↩

  9. 出处:「7 Errors」第 232 段(text/26-ch07-7-errors.txt:232,搜「non-standard additions」)。 ↩

  10. 出处:「7 Errors」第 570 段(text/26-ch07-7-errors.txt:570,搜「a shift in how JavaScript approaches errors」)。 ↩

  11. 出处:「7 Errors」第 256 段(text/26-ch07-7-errors.txt:256,搜「Promise.any()」)。errors 数组的用途见第 289 段(搜「e.errors[0]」)。 ↩

  12. 出处:「7 Errors」第 289 段(text/26-ch07-7-errors.txt:289,搜「errors property is what differentiates」)。 ↩

  13. 出处:「7 Errors」第 522 段(text/26-ch07-7-errors.txt:522,搜「cause property」)。 ↩

  14. 出处:「7 Errors」第 535–551 段(text/26-ch07-7-errors.txt:535,搜「processPayment」)。 ↩

  15. 出处:「7 Errors」第 307–355 段(text/26-ch07-7-errors.txt:313,搜「Symbol.dispose」;:346 搜「err.error」)。 ↩

  16. 出处:「7 Errors」第 570 段(text/26-ch07-7-errors.txt:570,搜「serve dual purposes」)。 ↩

  17. 出处:「7 Errors」第 396–498 段(text/26-ch07-7-errors.txt:396,搜「parallel error system」;类型表在 :429 起,搜「NetworkError」)。 ↩

  18. 出处:「7 Errors」第 504 段(text/26-ch07-7-errors.txt:504,搜「guide retry logic」)。 ↩

  19. 出处:「7 Errors」第 504 段(text/26-ch07-7-errors.txt:504,搜「equally useless without stack traces」)。 ↩

  20. 出处:「7 Errors」第 510 段(text/26-ch07-7-errors.txt:510,搜「second only to basic Error」)。 ↩

  21. 出处:「7 Errors」第 601–616 段(text/26-ch07-7-errors.txt:607,搜「matched the Error message exactly」)。 ↩

  22. 出处:「7 Errors」第 622 段(text/26-ch07-7-errors.txt:622,搜「adding a period」)。 ↩

  23. 出处:「7 Errors」第 634–656 段(text/26-ch07-7-errors.txt:645,搜「case 'ENOENT'」;错误码分类见第 668–674 段,搜「System errors」)。 ↩

  24. 出处:「7 Errors」第 679 段(text/26-ch07-7-errors.txt:679,搜「code is for machines」)。 ↩

  25. 出处:「7 Errors」第 697 段(text/26-ch07-7-errors.txt:697,搜「solely for debugging, never recovery」)。 ↩

  26. 出处:「7 Errors」第 697 段(text/26-ch07-7-errors.txt:697,搜「implementation addition that every JavaScript engine independently created」)。 ↩

  27. 出处:「7 Errors」第 761 段与第 761 段(text/26-ch07-7-errors.txt:761,搜「structured collection of frame objects」;:843 搜「serialized into a string」)。 ↩

  28. 出处:「7 Errors」第 813 段(text/26-ch07-7-errors.txt:813,搜「couple thousand bytes」)。 ↩

  29. 出处:「7 Errors」第 774–799 段(text/26-ch07-7-errors.txt:774,搜「prevailing wisdom」;:793 搜「deoptimization」)。 ↩

  30. 出处:「7 Errors」第 861 段(text/26-ch07-7-errors.txt:861,搜「defer converting」)。 ↩

  31. 出处:「7 Errors」第 864–875 段(text/26-ch07-7-errors.txt:864,搜「stackTraceLimit」;:875 搜「Do not capture any Frames」)。 ↩

  32. 出处:「7 Errors」第 885 段(text/26-ch07-7-errors.txt:885,搜「the more debugging information」)。 ↩

  33. 出处:「7 Errors」第 59–120 段(text/26-ch07-7-errors.txt:59,搜「dual, and often opposing」)。两个错误对象的代码见第 92–114 段。 ↩

  34. 出处:「7 Errors」第 92–97 段(text/26-ch07-7-errors.txt:93,搜「NETWORK_ERROR」):「这足以让应用知道问题性质和怎么恢复。」 ↩

  35. 出处:「7 Errors」第 109–114 段(text/26-ch07-7-errors.txt:110,搜「Connection to database timed out」):开发者需要的四类信息。 ↩

  36. 出处:「7 Errors」第 120 段(text/26-ch07-7-errors.txt:120,搜「far greater performance cost」):「应用要的信息收集便宜,开发者要的信息收集昂贵;而语言的基础定义两边都不够,于是各方补齐,都有代价。」 ↩

  37. 出处:「7 Errors」第 158–175 段(text/26-ch07-7-errors.txt:173,搜「Might be undefined or even throw」)。 ↩

  38. 出处:「7 Errors」第 185 段(text/26-ch07-7-errors.txt:185,搜「standardized on always throwing Error objects」)。 ↩

  39. 出处:「7 Errors」第 158–185 段(text/26-ch07-7-errors.txt:158,搜「never just blindly assume」):「catch 到的东西,绝不能盲目假设它是带标准属性的 Error 对象。」「先 instanceof 分流」是我们的实践建议,机制依据同段。 ↩

  40. 出处:「7 Errors」第 256–289 段(text/26-ch07-7-errors.txt:270,搜「Promise.any(promises)」)。 ↩

  41. 出处:「7 Errors」第 289 段(text/26-ch07-7-errors.txt:289,搜「what differentiates」):errors 属性同时服务恢复与调试。 ↩

  42. 出处:「7 Errors」第 295 段(text/26-ch07-7-errors.txt:295,搜「possible but rare」)。 ↩

  43. 出处:「7 Errors」第 307–355 段(text/26-ch07-7-errors.txt:320,搜「DatabaseConnection」)。 ↩

  44. 出处:「7 Errors」第 313 段(text/26-ch07-7-errors.txt:313,搜「Symbol.dispose」):using 自动调 dispose;「没有这个容器就得丢一个」见第 335 段(搜「you'd lose either」)。 ↩

  45. 出处:「7 Errors」第 535–556 段(text/26-ch07-7-errors.txt:535,搜「processPayment」)。 ↩

  46. 出处:「7 Errors」第 564 段(text/26-ch07-7-errors.txt:564,搜「largely academic」):「cause 只放 Error 还是放任意值之争基本是学究之争。」 ↩

  47. 出处:「7 Errors」表 7.1(text/26-ch07-7-errors.txt:444,搜「NetworkError」;八行依次列出)。 ↩

  48. 出处:「7 Errors」第 504 段(text/26-ch07-7-errors.txt:504,搜「guide retry logic」):「DOMException 的类型能用于恢复,因为它们描述操作失败;原生类型描述语言规则违反,给不出恢复指导。」 ↩

  49. 出处:「7 Errors」第 232 段(text/26-ch07-7-errors.txt:232,搜「your code is broken」):「TypeError 还是 ReferenceError 与重试/降级/中止无关——两者都只是『你的代码坏了』。」 ↩

  50. 出处:「7 Errors」第 504 段(text/26-ch07-7-errors.txt:504,搜「equally useless without stack traces」)。 ↩

  51. 出处:「7 Errors」第 510 段(text/26-ch07-7-errors.txt:510,搜「second only」)。 ↩ ↩2

  52. 出处:「7 Errors」第 662–674 段(text/26-ch07-7-errors.txt:668,搜「System errors」;三类依次列出)。 ↩ ↩2

  53. 出处:「7 Errors」第 640–656 段(text/26-ch07-7-errors.txt:645,搜「case 'ENOENT'」)。 ↩ ↩2

  54. 出处:「7 Errors」第 679 段(text/26-ch07-7-errors.txt:679,搜「static, machine-readable identifiers」):「错误码给机器定恢复策略,错误消息给人调试;消息因此可以自由演化。」 ↩ ↩2

  55. 出处:「7 Errors」第 622 段(text/26-ch07-7-errors.txt:622,搜「adding a period」)。 ↩

  56. 出处:「7 Errors」第 819–848 段(text/26-ch07-7-errors.txt:819,搜「two places」):「栈本身在两处加成本:创建 Error 时的抓取,和首次访问 stack 时的序列化。」 ↩

  57. 出处:「7 Errors」第 831 段(text/26-ch07-7-errors.txt:831,搜「individually quite expensive」):逐帧提取需要多次内存访问。 ↩

  58. 出处:「7 Errors」第 807 段(text/26-ch07-7-errors.txt:807,搜「none of the details are shared」):「每个 Error 都得自己记,细节在 Error 之间不共享。」 ↩

  59. 出处:「7 Errors」第 843 段(text/26-ch07-7-errors.txt:843,搜「serialized into a string」):「第一次访问 stack 才序列化;从不访问就不发生,但帧对象仍在 Error 里。」 ↩

  60. 出处:「7 Errors」第 861–875 段(text/26-ch07-7-errors.txt:864,搜「stackTraceLimit」;:875 搜「Do not capture any Frames」)。 ↩

  61. 出处:「7 Errors」第 885 段(text/26-ch07-7-errors.txt:885,搜「the more debugging information」):「调试信息抓得越多,建 Error 越慢、内存越高;对恢复毫无帮助。」 ↩

  62. 出处:「7 Errors」第 774–799 段(text/26-ch07-7-errors.txt:774,搜「prevailing wisdom」;「跟 throw 无关,是突然跑进没跑过的分支」见第 799 段)。 ↩

  63. 出处:「7 Errors」第 197 段与第 197 段(text/26-ch07-7-errors.txt:197,搜「native」;:697 搜「does not mention」)。 ↩

  64. 出处:「7 Errors」第 622 段(text/26-ch07-7-errors.txt:622,搜「broke the popular npm package manager」)。 ↩

  65. 出处:「7 Errors」第 364 段(text/26-ch07-7-errors.txt:364,搜「rarely used in practice」)。 ↩

  66. 出处:「7 Errors」第 813 段(text/26-ch07-7-errors.txt:813,搜「A single Error object can cost」)。 ↩

On this page