JavaScript 错误处理
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
程序运行出错不可怕,可怕的是错误被静默吞掉,最后表现为「数据不对但不知道哪里错了」。这一篇讲如何抛出、捕获和传递错误。
Error 对象
const err = new Error("文件不存在");
err.name; // "Error"
err.message; // "文件不存在"
err.stack; // 调用栈字符串,调试用
Error 只是普通对象,创建它不会中断程序;要中断必须用 throw。
常见内置错误类型:
| 类型 | 触发场景 |
|---|---|
TypeError |
对错误类型做操作,如 null.name、nums.map 不是函数 |
ReferenceError |
访问未声明的变量 |
SyntaxError |
代码语法错误(JSON.parse 遇到非法字符串也抛它) |
RangeError |
数值越界,如 new Array(-1) |
throw:主动中断
function divide(a, b) {
if (b === 0) {
throw new Error("除数不能为 0");
}
return a / b;
}
抛出后函数立即结束,后面的代码不执行,控制权交给最近的 catch;没有 catch 则程序终止(Node 里进程退出,浏览器里控制台报错)。
只 throw Error 实例:
throw new Error("出错了"); // 正确
throw "出错了"; // 不推荐:没有 stack,catch 里拿不到结构化信息
try / catch / finally
try {
const data = JSON.parse(raw);
console.log(data);
} catch (err) {
console.error("解析失败:", err.message);
} finally {
console.log("这一步总会执行");
}
执行顺序:
try里出错 → 跳到catch,出错点之后的代码不执行。- 无论是否出错、
catch里是否又有return或throw,finally总会执行。 finally常用于释放资源:关闭文件、清除定时器、恢复加载状态。
let busy = true;
try {
await loadData();
} finally {
busy = false; // 出错也一定恢复状态
}
catch 里的错误类型判断
try {
JSON.parse(raw);
} catch (err) {
if (err instanceof SyntaxError) {
console.log("数据格式不合法");
} else {
throw err; // 不认识的错误继续向上抛
}
}
「处理你能处理的,其余继续抛」是关键习惯。无差别吞掉所有错误会让问题消失在日志里。
ES2022 起可以省略 catch 参数:
try {
doSomething();
} catch {
console.log("失败了"); // 不关心错误细节
}
自定义错误类型
业务代码里区分错误类型比读 message 字符串可靠:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function requireName(user) {
if (!user.name) {
throw new ValidationError("缺少用户名", "name");
}
}
try {
requireName({});
} catch (err) {
if (err instanceof ValidationError) {
console.log(`${err.field}: ${err.message}`); // "name: 缺少用户名"
}
}
继承时记得设置 this.name,否则 err.name 会是 "Error"。
错误上下文:cause
try {
readConfig();
} catch (err) {
throw new Error("启动失败", { cause: err });
}
上层通过 err.cause 拿到原始错误,避免为了保留原因去拼接 message 字符串。Node 18+ 与所有现代浏览器支持 cause。
异步错误不能用 try / catch 直接捕获
这是最容易踩的坑:
// 错误写法:catch 不到
try {
setTimeout(() => {
throw new Error("异步出错");
}, 100);
} catch (err) {
console.log("捕获到了"); // 永远不会执行
}
回调执行时 try 块早已结束,错误发生在另一个调用栈里。正确做法取决于异步风格:
Promise 用 .catch():
fetchUsers()
.then((users) => console.log(users))
.catch((err) => console.error("请求失败:", err.message));
async / await 可以用 try / catch(推荐):
async function load() {
try {
const users = await fetchUsers();
return users;
} catch (err) {
console.error("请求失败:", err.message);
return [];
}
}
Promise 链的末尾必须挂 .catch(),否则会产生未处理的 rejection(Node 里会打印警告或在未来版本中直接终止进程)。
输入校验优先于异常
异常应该表示「异常情况」,不要用 try / catch 代替正常的判断:
// 不推荐:把正常分支交给异常
try {
const value = obj.field;
} catch {
const value = "默认";
}
// 推荐:直接判断
const value = obj?.field ?? "默认";
常见错误与注意事项
- 空的
catch {}:吞掉错误,问题被隐藏。至少要记录日志,或写明为什么可以忽略。 throw字符串或普通对象:拿不到stack,instanceof也无法判断类型。- 用
try / catch包setTimeout回调:捕获不到异步错误,改用 Promise +await。 finally里return:会覆盖try/catch的返回值,通常不是你想要的。- Promise 链忘记
.catch():未处理的 rejection 难以定位。 - 自定义错误不设置
name:日志里清一色"Error",排查困难。
小结
throw只抛Error实例;new Error(msg, { cause })保留原始原因。try / catch / finally里finally一定执行,适合清理。- 只处理认识的错误,其余用
throw err继续上抛。 - 异步错误靠
.catch()或await+try / catch,直接try包回调无效。 - 正常分支用判断,不要用异常代替。