nullthrows vs. try...catch:两种错误处理哲学的较量
在JavaScript 和 TypeScript 开发中,处理潜在的 null 或 undefined 值对于编写健壮的代码至关重要。虽然 try...catch 块是一种基础的错误处理机制,但像 nullthrows 这样的断言式函数提供了一种不同的、更主动的方法。让我们通过一个真实世界的代码示例来探讨它们的区别和优势。
代码示例: inject.ts
这是一个来自 Chrome 扩展内容脚本的代码片段。它的工作是将另一个脚本(backend)注入到网页中。
import backend from 'raw-loader!./backend'
function nullthrows<T>(x: T | null | undefined, message?: string): T {
if (x != null) {
return x
}
const error: any = new Error(
message !== undefined ? message : 'Got unexpected ' + x
)
error.framesToPop = 1 // 跳过 nullthrows 自己的堆栈帧
throw error
}
function injectCode(code) {
const script = document.createElement('script')
script.textContent = code
// 此脚本在 <head> 元素创建之前运行,
// 因此我们将其添加到 <html> 元素上。
nullthrows(document.documentElement).appendChild(script)
nullthrows(script.parentNode).removeChild(script)
}
injectCode(`;(function(){
var exports = {};
${backend}
})()`)
nullthrows 的方法:防御性断言
上面看到的 nullthrows 函数是“断言函数”或“卫语句”的经典示例。其哲学与 try...catch 有着根本的不同。
nullthrows 的主要优势
-
快速失败与早期检测:
nullthrows在变量即将被使用的那一刻检查其是否为null或undefined。如果值无效,它会立即抛出错误,停止执行。这可以防止null值在应用程序中进一步传播,从而导致更隐蔽的错误。- 在我们的示例中,
nullthrows(document.documentElement)确保我们甚至不会尝试在null对象上调用.appendChild(script)。而try...catch仅在失败的尝试之后才会捕获TypeError。
-
更清晰的意图和自文档化代码:
- 使用
nullthrows向任何阅读代码的人明确地表明:“我,作为开发者,断言此值在此处不能为 null。如果为 null,那就是一个 bug。” - 这使得代码的假设变得透明。
try...catch块则更为模糊;它可能正在处理一个预期的运行时错误(如网络请求失败),也可能是一个意外的程序 bug。nullthrows消除了这种模糊性。
- 使用
-
精确且信息丰富的错误:
nullthrows抛出的错误非常具体:“Got unexpected null”。这立即告诉开发人员哪里出了问题。try...catch可能会捕获一个更通用的TypeError: Cannot read properties of null (reading 'appendChild')。虽然也是一个错误,但它需要额外的推导步骤才能弄清楚哪个变量是null。
-
TypeScript 中的类型收窄:
- 对于 TypeScript 用户来说,断言函数提供了一个强大的好处。当一个变量
x通过nullthrows(x)后,TypeScript 编译器就会明白x不再是null或undefined。这将类型从T | null | undefined“收窄”为T,从而允许您在不进行进一步检查的情况下访问其属性,并满足严格的空值检查编译器选项。
- 对于 TypeScript 用户来说,断言函数提供了一个强大的好处。当一个变量
try...catch:用于可预期的、可恢复的错误
try...catch 仍然是处理可预期的并且程序可以从中恢复的错误的理想工具。这些通常是超出程序员直接控制范围的外部故障。
try...catch 的良好用例:
- 网络请求 (
fetch,axios) - 文件系统操作(读/写文件)
- 解析用户输入或外部数据 (
JSON.parse)
总结
根据错误的性质选择您的错误处理工具:
- 对内部不变量和程序 bug 使用
nullthrows风格的断言。 将这些视为对代码契约的违反。目标是尽早、响亮、清晰地崩溃,以便于调试。在inject.ts示例中,document.documentElement在有效的浏览器上下文中永远不应为 null,这使其成为断言的完美候选者。 - 对外部的、运行时的错误使用
try...catch。 在这些情况下,您预计会发生故障,并希望优雅地处理它,例如向用户显示消息、重试操作或回退到默认状态。