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 降本两招:惰性序列化 + 限深度¶
引擎和开发者各有一招降本:
- 惰性序列化:内部帧到字符串的转换推迟到你第一次访问
.stack; 从不访问,就不花这个钱30。 Error.stackTraceLimit(V8 的非标准属性):控制抓几帧。 设成 5 只留 5 帧;设成 0 一帧不抓31。
书里的经验法则跟着结论:抓的调试信息越多,建 Error 越慢、内存越高; 而这些信息对恢复毫无帮助32。生产系统下调栈深度,是性价比很高的优化—— 代价是事故排查时现场变薄,这个权衡属于「两个目的对立」的又一个实例。
3.1b 双重目的的完整论证:两个错误对象对照¶
7.1 节的论证值得用原书的对照实验完整重述一遍,因为它是全章的题眼37。
应用那侧,只需要三个字段就够做决策:
「网络失败了、可以重试、有降级方案」——恢复策略当场就能定38。
开发者那侧,同样一次失败要这些才够定位:
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' }
};
原因、位置、时间、受影响的背景信息,全是给人看的39。
书里随即点破这组对照的不对称:应用要的那点信息,收集起来很便宜;
开发者要的现场,收集起来昂贵得多——而语言只给了 Error{name, message}
这点家底,两边都不够,于是引擎、运行时、应用各自想办法补齐,
每种补齐都有自己的价钱40。这句「都有价钱」是 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——异常处理代码自己变成异常源41。
现代生态的公约数是「永远抛 Error 对象」,理由朴素:抛任意值确实没什么用,
Error 至少带着 name、message,外加工 引擎附送的 stack42。
但读别人的代码(尤其是老库)时,这个防御姿势不能省:
先确认是对象、再读属性,或者干脆 e instanceof Error 分流43。
3.3b 现代补丁三连的代码级细节¶
AggregateError 的完整用法(ES2021,为 Promise.any() 而生)44:
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 数组是它区别于老七样的唯一结构差异,也是它同时服务两个目的
的载体:恢复侧逐个查「哪个操作挂了」,调试侧一次拿到三份栈45。
除 Promise.any() 之外的使用「可能但罕见」46。
SuppressedError 的完整用法(ES2024,收尾清理的保险丝)47:
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 里——
没有这个容器,原来的错和清理的错总得死一个48。
cause 的包法(ES2022,边界处的标准动作)49:
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 的值」有争论,书里定性为「学究之争」—— 要紧的是它给了标准做法:边界上抛干净的、链条上保完整的50。
3.4b DOMException 的全表与「为什么它更像对的答案」¶
DOMException 的常用类型,原书给了八行表,照录51:
| 类型 | 含义 |
|---|---|
| NetworkError | 网络操作失败 |
| SecurityError | 操作违反安全策略 |
| NotAllowedError | 用户拒绝了权限 |
| QuotaExceededError | 存储配额超限 |
| AbortError | 操作被中止 |
| TimeoutError | 操作超时 |
| InvalidStateError | 对象处于错误状态 |
| DataCloneError | 值无法被克隆 |
注意这张表的命名角度:每一行都是一次「应用层的操作失败」,
而不是一条「语言规则违反」——QuotaExceededError 告诉你
「清缓存或提示用户」,NetworkError 告诉你「重试或降级」,
这就是书里「DOMException 的名字能指导恢复」判断的依据52。
对照原生七样的 TypeError/RangeError:它们说的是
「你代码写错了」,对恢复策略零指导——「重试?你的代码还是错的。」53
调试侧两边打平:没有 stack,谁都帮不上54。
作者补了一句使用广度:DOMException 是开发者最常遇到的错误体系,
仅次于 Error 和 TypeError55。
3.5b 错误码体系的全景¶
Node.js 借 DOMException 思路建立的码体系,书里分了类56:
- 系统错误:
ENOENT、EACCES、EMFILE……来自操作系统, 大写字母开头,是 Node 里最古老的一批; - 断言错误:
ERR_ASSERTION,expectation 被违反; - 类型/参数错误:
ERR_INVALID_ARG_TYPE等,Node 自己校验参数时抛。
switch 分支的样板代码原书给了,值得照抄骨架57:
try {
await someOperation();
} catch (e) {
switch (e.code) {
case 'ENOENT': // 文件不存在——也许该创建
break;
case 'EACCES': // 权限不足——致命,往上抛
throw e;
case 'EMFILE': // 打开文件太多——稍后重试
await delay(100);
return retry();
}
}
「码给机器、消息给人」的分工确立之后,消息终于可以自由演化 (改措辞、加细节、补建议)而不怕砸烂依赖恢复逻辑的应用—— 因为应用依赖的是「几乎不变的机器可读标识符」58。 再对比前节的教训:当年一个句点就能搞崩 npm,因为恢复逻辑绑在消息上; 如今绑在码上,消息随便改59。从「匹配文本」到「匹配码」, 是一次接口设计的范式迁移,代价是一整个生态的兼容债。
3.6b 调用栈的成本解剖:两笔开销、两个对策¶
栈的成本拆成两笔,书里分得很清60:
第一笔:抓取。 Error 创建的那一刻,引擎要回溯「我是怎么走到这的」, 逐帧提取函数名和源码位置——每一步都是好几次内存访问, 单个看都贵61。这笔钱的残酷在于不可分摊: 每个 Error 各自记各自的现场,帧信息在 Error 之间不共享62。
第二笔:序列化。 第一次访问 .stack 时,
存的帧对象被格式化成字符串然后释放——要访问 V8 的源码信息,
这步不便宜;从不访问,这步就不发生,但帧对象照样占着内存63。
两个对策也因此各管一笔:惰性序列化(第一访问才格式化)管第二笔;
Error.stackTraceLimit(V8 非标准属性)管第一笔——设 5 帧只留 5 帧,
设 0 一帧不抓64。经验法则跟着来:抓的调试信息越多,
建 Error 越慢、内存越高;而这对恢复毫无帮助65。
「恢复与调试两个目的的对立」,在这里是字面意义的成本对立。
至于「try-catch 让 V8 不去优化」的老迷思,书里专门辟了一段辟谣: 曾经是真的,现在不是——V8 优化带 try-catch 的函数毫无压力; 真正的陷阱是优化器只优化跑过的代码:一个被调用几千次、 catch 块一次没触发过的函数,它的优化版本里根本没有 catch 块; 某天突然来了个会触发 throw 的输入,被迫去优化—— 不是 throw 的错,是突然跑进了从没跑过的分支66。
3.0b 章首坐标:原书给这一章立的五个问题¶
第 7 章开篇的「本章覆盖」清单,其实是五个层层递进的问题, 照录下来当本章的导览55:
- 错误如何同时服务「恢复」与「调试」两种需要? —— 7.1 节,双重目的;
- 为什么调用栈既珍贵又昂贵? —— 7.4 节,栈的成本与对策;
- 错误处理器该放在哪? —— 这是 12 章 3.2 节的内容,但框架在本章立起: 「语义边界」四个字在 7.1 的双重目的论证里就已埋下;
- 异步操作为什么会打断错误传播? —— 12 章 3.5 节的断层图;
- 全局错误处理器什么时候是最后一根稻草? —— 12 章 3.6 节的兜底体系。
五个问题里,前两个由本章(11)回答,后三个由下一章(12)回答; 而贯穿五个问题的主线只有一条:恢复要背景信息,调试要细节现场, 收集现场要钱——每一次设计决策(建不建栈、放不放 cause、 写不写 code)都是这三个量的权衡。 读这一章时带着这条主线, 十条总结就不是散点,而是一棵树的枝叶。
3.6c 第 7 章与全书的缝合:错误的三层归属¶
读到这里,全书的三层框架可以最后一次兑现:错误体系里, 每一层都在补上一层的空缺,而且补法各有性格56:
- 语言层只给了
Error{name, message}和「随便 throw」的宽门框, 留下的空洞最大:没有恢复语义、没有调用栈、没有错误链; - 引擎层补的是「开发者侧」:V8 自己发明了 stack trace (规范里没有!),并给它做了惰性序列化——调试体验是引擎送的,不是语言给的;
- 宿主与生态层补的是「应用侧」:W3C 造了 DOMException 的语义命名, Node.js 造了机器可读的错误码,ES2021/2022/2024 的 AggregateError/cause/SuppressedError 则是语言层迟到的大补丁57。
把这个归属记牢,遇到任何错误相关的「为什么」都有了排查路线: 栈不对找引擎版本,码不对找 Node 文档,类型语义不对找 Web 标准, 而「能不能 throw 这个值」——语言层从一开始就说:随便58。
这也解释了为什么错误一章放在对象章之后:错误对象本身就是普通对象 (可以挂任意属性、走原型链),引擎和宿主只是往这个普通对象上 多钉了些钉子(message、stack、code、cause、errors)。 理解了对象,错误体系就没有新东西;不理解对象,错误体系处处是魔法。
作者的判断与证据¶
有证据的判断:
作者的主张:
- 「原生类型区分无用、DOMException 命名才有恢复价值」是锐利的判断, 但「调试侧两者一样没用」略显绝对——类型信息在过滤日志、聚合告警时仍有价值。
- 「下调 stackTraceLimit」是实践建议,代价(排查现场变薄)需要团队自己权衡。
判断(我们的,不是书里的): 本章最重要的洞察是「错误类型命名学」的对照: 原生错误按「违反了哪条语言规则」命名,对恢复毫无指导;DOMException/错误码按 「应用发生了什么」命名,天然服务恢复逻辑。这个对照可以照搬到任何语言/框架的 错误体系设计上——先问「读者是机器还是人,他要做什么决策」。 如果错,会错在: 如果团队把全部精力投入错误分类学而不解决「错误有没有被 送到能处理它的地方」(12 章的边界问题),分类再漂亮也是屠龙之技——两章要合读。
边界与局限¶
- SuppressedError/
using属 ES2024 新特性,书中也承认实践中极少用到35。 Error.stackTraceLimit是 V8 私有属性,别的引擎不保证; 浏览器环境下调栈深度的替代方案书中没讲。- 错误监控体系(Sentry 类)如何把堆传上服务器、序列化格式,全在范围外。
- 「几千字节一个 Error」没给测量条件(帧数、引擎版本),只能当量级参考36。
可带走的¶
- 规范只管 Error{name,message};stack trace、错误码都是引擎/宿主私货。
- throw 可以抛任何东西;catch 到的值先确认是不是 Error 再读属性。
- 原生七个类型只差名字,不给恢复建议;别再发明同类新类型。
- DOMException 按「什么操作失败了」命名,才有恢复指导价值。
- 错误码的教训:机器匹配 code,人读 message;消息演化,码不变。
- cause 用来在边界包错误:对外干净,对内保完整链。
- 一个 Error 几千字节;恢复路径上的错误创建是真实成本。
- try-catch 本身不再拖慢优化;去优化源于「突然跑进没跑过的分支」。
- 调 Error.stackTraceLimit 可以省钱;栈只为调试,永不服务恢复。
原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 「约定大于规范」总论 | 7 Errors | text/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」) |
-
出处:「7 Errors」第 47 段(
text/26-ch07-7-errors.txt:47,搜「convention than by specification」)。 ↩ -
出处:「7 Errors」第 132 段(
text/26-ch07-7-errors.txt:132,搜「capital-E Errors」)。 ↩ -
出处:「7 Errors」第 132 段(
text/26-ch07-7-errors.txt:132,搜「interrupt normal code execution」)。 ↩ -
出处:「7 Errors」第 139–148 段(
text/26-ch07-7-errors.txt:140,搜「just a string」)。 ↩ -
出处:「7 Errors」第 158–175 段(
text/26-ch07-7-errors.txt:173,搜「Might be undefined or even throw」)。 ↩ -
出处:「7 Errors」第 185 段(
text/26-ch07-7-errors.txt:185,搜「standardized on always throwing Error objects」)。 ↩ -
出处:「7 Errors」第 197–227 段(
text/26-ch07-7-errors.txt:197,搜「nine built-in」;EvalError 的「Deprecated, never thrown」见第 221 段)。 ↩ -
出处:「7 Errors」第 232 段(
text/26-ch07-7-errors.txt:232,搜「structurally identical」)。「TypeError 还是 ReferenceError 与重试/降级/中止无关」在同段。 ↩ -
出处:「7 Errors」第 232 段(
text/26-ch07-7-errors.txt:232,搜「non-standard additions」)。 ↩ -
出处:「7 Errors」第 570 段(
text/26-ch07-7-errors.txt:570,搜「a shift in how JavaScript approaches errors」)。 ↩ -
出处:「7 Errors」第 256 段(
text/26-ch07-7-errors.txt:256,搜「Promise.any()」)。errors 数组的用途见第 289 段(搜「e.errors[0]」)。 ↩ -
出处:「7 Errors」第 289 段(
text/26-ch07-7-errors.txt:289,搜「errors property is what differentiates」)。 ↩ -
出处:「7 Errors」第 522 段(
text/26-ch07-7-errors.txt:522,搜「cause property」)。 ↩ -
出处:「7 Errors」第 535–551 段(
text/26-ch07-7-errors.txt:535,搜「processPayment」)。 ↩ -
出处:「7 Errors」第 307–355 段(
text/26-ch07-7-errors.txt:313,搜「Symbol.dispose」;:346搜「err.error」)。 ↩ -
出处:「7 Errors」第 570 段(
text/26-ch07-7-errors.txt:570,搜「serve dual purposes」)。 ↩ -
出处:「7 Errors」第 396–498 段(
text/26-ch07-7-errors.txt:396,搜「parallel error system」;类型表在:429起,搜「NetworkError」)。 ↩ -
出处:「7 Errors」第 504 段(
text/26-ch07-7-errors.txt:504,搜「guide retry logic」)。 ↩ -
出处:「7 Errors」第 504 段(
text/26-ch07-7-errors.txt:504,搜「equally useless without stack traces」)。 ↩ -
出处:「7 Errors」第 510 段(
text/26-ch07-7-errors.txt:510,搜「second only to basic Error」)。 ↩ -
出处:「7 Errors」第 601–616 段(
text/26-ch07-7-errors.txt:607,搜「matched the Error message exactly」)。 ↩ -
出处:「7 Errors」第 622 段(
text/26-ch07-7-errors.txt:622,搜「adding a period」)。 ↩ -
出处:「7 Errors」第 634–656 段(
text/26-ch07-7-errors.txt:645,搜「case 'ENOENT'」;错误码分类见第 668–674 段,搜「System errors」)。 ↩ -
出处:「7 Errors」第 679 段(
text/26-ch07-7-errors.txt:679,搜「code is for machines」)。 ↩ -
出处:「7 Errors」第 697 段(
text/26-ch07-7-errors.txt:697,搜「solely for debugging, never recovery」)。 ↩ -
出处:「7 Errors」第 697 段(
text/26-ch07-7-errors.txt:697,搜「implementation addition that every JavaScript engine independently created」)。 ↩ -
出处:「7 Errors」第 761 段与第 761 段(
text/26-ch07-7-errors.txt:761,搜「structured collection of frame objects」;:843搜「serialized into a string」)。 ↩ -
出处:「7 Errors」第 813 段(
text/26-ch07-7-errors.txt:813,搜「couple thousand bytes」)。 ↩ -
出处:「7 Errors」第 774–799 段(
text/26-ch07-7-errors.txt:774,搜「prevailing wisdom」;:793搜「deoptimization」)。 ↩ -
出处:「7 Errors」第 861 段(
text/26-ch07-7-errors.txt:861,搜「defer converting」)。 ↩ -
出处:「7 Errors」第 864–875 段(
text/26-ch07-7-errors.txt:864,搜「stackTraceLimit」;:875搜「Do not capture any Frames」)。 ↩ -
出处:「7 Errors」第 885 段(
text/26-ch07-7-errors.txt:885,搜「the more debugging information」)。 ↩ -
出处:「7 Errors」第 197 段与第 197 段(
text/26-ch07-7-errors.txt:197,搜「native」;:697搜「does not mention」)。 ↩ -
出处:「7 Errors」第 622 段(
text/26-ch07-7-errors.txt:622,搜「broke the popular npm package manager」)。 ↩ -
出处:「7 Errors」第 364 段(
text/26-ch07-7-errors.txt:364,搜「rarely used in practice」)。 ↩ -
出处:「7 Errors」第 813 段(
text/26-ch07-7-errors.txt:813,搜「A single Error object can cost」)。 ↩ -
出处:「7 Errors」第 59–120 段(
text/26-ch07-7-errors.txt:59,搜「dual, and often opposing」)。两个错误对象的代码见第 92–114 段。 ↩ -
出处:「7 Errors」第 92–97 段(
text/26-ch07-7-errors.txt:93,搜「NETWORK_ERROR」):「这足以让应用知道问题性质和怎么恢复。」 ↩ -
出处:「7 Errors」第 109–114 段(
text/26-ch07-7-errors.txt:110,搜「Connection to database timed out」):开发者需要的四类信息。 ↩ -
出处:「7 Errors」第 120 段(
text/26-ch07-7-errors.txt:120,搜「far greater performance cost」):「应用要的信息收集便宜,开发者要的信息收集昂贵;而语言的基础定义两边都不够,于是各方补齐,都有代价。」 ↩ -
出处:「7 Errors」第 158–175 段(
text/26-ch07-7-errors.txt:173,搜「Might be undefined or even throw」)。 ↩ -
出处:「7 Errors」第 185 段(
text/26-ch07-7-errors.txt:185,搜「standardized on always throwing Error objects」)。 ↩ -
出处:「7 Errors」第 158–185 段(
text/26-ch07-7-errors.txt:158,搜「never just blindly assume」):「catch 到的东西,绝不能盲目假设它是带标准属性的 Error 对象。」「先 instanceof 分流」是我们的实践建议,机制依据同段。 ↩ -
出处:「7 Errors」第 256–289 段(
text/26-ch07-7-errors.txt:270,搜「Promise.any(promises)」)。 ↩ -
出处:「7 Errors」第 289 段(
text/26-ch07-7-errors.txt:289,搜「what differentiates」):errors 属性同时服务恢复与调试。 ↩ -
出处:「7 Errors」第 295 段(
text/26-ch07-7-errors.txt:295,搜「possible but rare」)。 ↩ -
出处:「7 Errors」第 307–355 段(
text/26-ch07-7-errors.txt:320,搜「DatabaseConnection」)。 ↩ -
出处:「7 Errors」第 313 段(
text/26-ch07-7-errors.txt:313,搜「Symbol.dispose」):using 自动调 dispose;「没有这个容器就得丢一个」见第 335 段(搜「you'd lose either」)。 ↩ -
出处:「7 Errors」第 535–556 段(
text/26-ch07-7-errors.txt:535,搜「processPayment」)。 ↩ -
出处:「7 Errors」第 564 段(
text/26-ch07-7-errors.txt:564,搜「largely academic」):「cause 只放 Error 还是放任意值之争基本是学究之争。」 ↩ -
出处:「7 Errors」表 7.1(
text/26-ch07-7-errors.txt:444,搜「NetworkError」;八行依次列出)。 ↩ -
出处:「7 Errors」第 504 段(
text/26-ch07-7-errors.txt:504,搜「guide retry logic」):「DOMException 的类型能用于恢复,因为它们描述操作失败;原生类型描述语言规则违反,给不出恢复指导。」 ↩ -
出处:「7 Errors」第 232 段(
text/26-ch07-7-errors.txt:232,搜「your code is broken」):「TypeError 还是 ReferenceError 与重试/降级/中止无关——两者都只是『你的代码坏了』。」 ↩ -
出处:「7 Errors」第 504 段(
text/26-ch07-7-errors.txt:504,搜「equally useless without stack traces」)。 ↩ -
出处:「7 Errors」第 12–30 段(
text/26-ch07-7-errors.txt:16,搜「How errors serve」):「本章覆盖」五项依次列出,问题的归章是我们的整理。 ↩↩ -
出处:「7 Errors」第 41–53 段(
text/26-ch07-7-errors.txt:41,搜「intricate web」):「开发者实际遇到的错误,是一张引擎与运行时特有的行为和历史偶然织成的网。」三层的具体分工见本章各节。 ↩↩ -
出处:「7 Errors」第 570 段(
text/26-ch07-7-errors.txt:570,搜「shift in how JavaScript approaches」):「cause、AggregateError、SuppressedError 代表了 JavaScript 处理错误方式的转变。」 ↩↩ -
出处:「7 Errors」第 132–148 段(
text/26-ch07-7-errors.txt:140,搜「just a string」):「JavaScript 不像多数语言,它允许你 throw 任何值。」 ↩↩ -
出处:「7 Errors」第 622 段(
text/26-ch07-7-errors.txt:622,搜「adding a period」)。 ↩ -
出处:「7 Errors」第 819–848 段(
text/26-ch07-7-errors.txt:819,搜「two places」):「栈本身在两处加成本:创建 Error 时的抓取,和首次访问 stack 时的序列化。」 ↩ -
出处:「7 Errors」第 831 段(
text/26-ch07-7-errors.txt:831,搜「individually quite expensive」):逐帧提取需要多次内存访问。 ↩ -
出处:「7 Errors」第 807 段(
text/26-ch07-7-errors.txt:807,搜「none of the details are shared」):「每个 Error 都得自己记,细节在 Error 之间不共享。」 ↩ -
出处:「7 Errors」第 843 段(
text/26-ch07-7-errors.txt:843,搜「serialized into a string」):「第一次访问 stack 才序列化;从不访问就不发生,但帧对象仍在 Error 里。」 ↩ -
出处:「7 Errors」第 861–875 段(
text/26-ch07-7-errors.txt:864,搜「stackTraceLimit」;:875搜「Do not capture any Frames」)。 ↩ -
出处:「7 Errors」第 885 段(
text/26-ch07-7-errors.txt:885,搜「the more debugging information」):「调试信息抓得越多,建 Error 越慢、内存越高;对恢复毫无帮助。」 ↩ -
出处:「7 Errors」第 774–799 段(
text/26-ch07-7-errors.txt:774,搜「prevailing wisdom」;「跟 throw 无关,是突然跑进没跑过的分支」见第 799 段)。 ↩