JavaScript 错误 - throw、try 和 catch
JavaScript 错误处理:throw、try 和 catch
在 JavaScript 中,错误处理是构建健壮应用程序的关键。通过 try...catch...finally 结构和 throw 语句,我们可以优雅地捕获、处理和抛出错误,防止程序崩溃并提供更好的用户体验。
一、错误类型 (Error Types)
JavaScript 内置了多种错误类型,了解它们有助于精准捕获和处理。
| 错误类型 | 描述 | 常见触发场景 |
|---|---|---|
Error |
通用错误基类 | 手动抛出或未知错误 |
SyntaxError |
语法错误 | eval() 中的非法语法,JSON 解析错误 |
ReferenceError |
引用错误 | 访问未声明的变量 |
TypeError |
类型错误 | 对非对象调用方法,null/undefined 调用 |
RangeError |
范围错误 | 数组长度非法,递归过深 |
URIError |
URI 错误 | decodeURI() 参数非法 |
EvalError |
Eval 错误 | eval() 使用不当 (现代 JS 中很少见) |
// 示例
let a;
console.log(a); // undefined (不是错误)
console.log(b); // ReferenceError: b is not defined
"hello".length; // 5 (正常)
(5).length; // TypeError: Cannot read property 'length' of number
JSON.parse("{a:1}"); // SyntaxError: Unexpected token a
二、try…catch…finally 结构
这是 JavaScript 处理同步错误的核心机制。
1. 基本语法
try {
// 可能抛出错误的代码
riskyOperation();
} catch (error) {
// 捕获并处理错误
console.error("发生错误:", error.message);
} finally {
// 无论是否出错都会执行的代码 (清理资源)
console.log("清理工作完成");
}
2. 执行流程
- try: 执行代码。
- 若无错误:跳过
catch,执行finally(如果有),然后继续后续代码。 - 若有错误:立即停止
try中剩余代码,跳转到catch。
- 若无错误:跳过
- catch: 接收错误对象。
- 执行错误处理逻辑。
- 如果
catch中再次抛出错误,finally仍会执行,然后错误继续向上传播。
- finally: 无论发生什么都会执行。
- 常用于关闭文件、清除定时器、隐藏加载动画等。
3. 代码示例
function divide(a, b) {
try {
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
} catch (err) {
console.error(`计算失败: ${err.message}`);
return null; // 返回默认值
} finally {
console.log("除法操作结束");
}
}
console.log(divide(10, 2)); // 5, "除法操作结束"
console.log(divide(10, 0)); // null, "计算失败: 除数不能为零", "除法操作结束"
三、throw 语句
throw 用于手动抛出错误。抛出的值可以是任何类型,但通常抛出 Error 对象或其子类。
1. 抛出标准错误对象
function validateAge(age) {
if (typeof age !== 'number') {
throw new TypeError("年龄必须是数字");
}
if (age < 0 || age > 150) {
throw new RangeError("年龄必须在 0 到 150 之间");
}
return true;
}
try {
validateAge("20"); // 抛出 TypeError
} catch (e) {
console.log(e.name); // "TypeError"
console.log(e.message); // "年龄必须是数字"
console.log(e.stack); // 堆栈跟踪信息
}
2. 抛出自定义错误 (ES2022+)
现代 JavaScript 允许直接继承 Error 创建自定义错误类,使错误处理更语义化。
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
// 保持堆栈跟踪正确 (在 V8 引擎中)
Error.captureStackTrace(this, ValidationError);
}
}
function registerUser(user) {
if (!user.email) {
throw new ValidationError("邮箱不能为空", "email");
}
if (!user.password || user.password.length < 6) {
throw new ValidationError("密码长度至少6位", "password");
}
}
try {
registerUser({ name: "Alice" });
} catch (err) {
if (err instanceof ValidationError) {
console.log(`字段 ${err.field} 验证失败:${err.message}`);
}
}
3. 抛出非 Error 对象 (不推荐)
虽然可以抛出字符串或数字,但这会导致调试困难,且丢失堆栈信息。
// 不推荐
throw "出错了";
throw 404;
// 推荐
throw new Error("出错了");
throw { code: 404, message: "Not Found" }; // 如果必须用对象,也要小心处理
四、错误处理最佳实践
1. 捕获特定错误
避免使用空的 catch 或捕获所有错误而不区分,这可能会掩盖真正的 bug。
// 不推荐:捕获所有错误并忽略
try {
risky();
} catch (e) {
// 什么都不做,错误被吞掉了
}
// 推荐:区分处理
try {
risky();
} catch (e) {
if (e instanceof NetworkError) {
showRetryButton();
} else if (e instanceof ValidationError) {
showFormError(e.field);
} else {
// 未知错误,记录日志并上报
console.error("未知错误", e);
reportToSentry(e);
}
}
2. 不要吞掉错误
在 catch 块中,如果无法处理,应该重新抛出 (throw e) 或记录日志。
try {
doSomething();
} catch (e) {
console.error("处理失败", e);
throw e; // 重新抛出,让上层处理
}
3. 使用 finally 清理资源
确保资源(如定时器、文件流、锁)被释放。
let timer;
try {
timer = setInterval(() => {
// ...
}, 1000);
// 可能出错的代码
} catch (e) {
console.error(e);
} finally {
if (timer) clearInterval(timer); // 确保定时器被清除
}
4. 异步错误处理
try...catch 不能直接捕获异步回调(如 setTimeout)中的错误,但可以捕获 async/await 中的错误。
场景 A: Promise 链
fetchData()
.then(data => processData(data))
.catch(err => {
console.error("Promise 错误:", err);
});
场景 B: Async/Await (推荐)
async function loadData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (err) {
console.error("加载失败:", err);
throw err; // 重新抛出
} finally {
setLoading(false);
}
}
场景 C: 全局未捕获错误
对于未被 catch 捕获的 Promise 拒绝,需要监听全局事件:
window.addEventListener('unhandledrejection', event => {
event.preventDefault(); // 阻止默认控制台报错
console.error('未处理的 Promise 错误:', event.reason);
});
五、常见陷阱与技巧
1. 错误对象属性
| 属性 | 含义 |
|---|---|
name |
错误名称 (如 “TypeError”) |
message |
错误描述信息 |
stack |
堆栈跟踪字符串 (调试神器) |
try {
undefined.func();
} catch (e) {
console.log(e.name); // "TypeError"
console.log(e.message); // "Cannot read properties of undefined"
console.log(e.stack); // 包含文件行号
}
2. 重新抛出错误
在 catch 中处理部分逻辑后,如果需要上层知道错误,必须 throw。
try {
// ...
} catch (e) {
logError(e);
// 忘记 throw e; -> 错误被吞掉,上层以为成功了
throw e;
}
3. 错误边界 (React 等框架)
在 UI 框架中,通常有专门的“错误边界”组件来捕获子组件渲染时的错误,防止整个应用崩溃。
4. 调试技巧
- 在
catch中打断点,查看error对象。 - 使用
console.trace()在抛出前打印调用栈。 - 使用
debugger语句配合try...catch定位问题。
六、总结
| 关键字 | 作用 | 关键点 |
|---|---|---|
try |
包裹可能出错的代码 | 必须与 catch 或 finally 配合使用 |
catch |
捕获并处理错误 | 接收错误对象,可区分类型处理 |
finally |
无论成败都执行的清理代码 | 常用于资源释放 |
throw |
手动抛出错误 | 推荐抛出 Error 对象或子类 |
核心原则:
- 不要吞掉错误:要么处理,要么抛出。
- 精准捕获:尽量捕获特定类型的错误。
- 保持堆栈:抛出错误时保留原始堆栈信息。
- 异步处理:使用
async/await+try/catch处理异步错误。
掌握这些机制,能让你的 JavaScript 代码更加健壮、易维护!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)