Appearance
15|JavaScript 错误处理与异常
代码运行过程中不可避免地会出现错误:网络请求超时、用户输入非法、DOM 元素不存在。没有错误处理的代码在出错时会直接崩溃,页面卡死或功能不可用。try/catch 和合理的防御式编程,能把不可控的异常转为可控的错误响应。
一、try / catch / finally
基本结构
js
try {
const data = JSON.parse(userInput);
console.log(data.name);
} catch (err) {
console.error("解析失败:", err.message);
} finally {
console.log("无论成功失败,这里都会执行");
}执行流程:
try块中的代码正常执行- 如果抛出异常,执行权跳转到
catch块 - 无论是否发生异常,
finally块都会执行
catch 参数(上面例子中的 err)是一个 Error 对象,包含:
| 属性 | 含义 |
|---|---|
err.message | 错误描述信息 |
err.name | 错误类型(如 SyntaxError、TypeError) |
err.stack | 调用栈追踪(调试用) |
抛出错误
js
function divide(a, b) {
if (b === 0) {
throw new Error("除数不能为 0");
}
return a / b;
}
try {
divide(10, 0);
} catch (err) {
console.error(err.message); // "除数不能为 0"
}可以用内置的 Error 子类表达更具体的错误:
js
throw new TypeError("参数必须是数字");
throw new RangeError("索引超出范围");
throw new SyntaxError("JSON 格式错误");二、常见错误类型
| 类型 | 触发场景 |
|---|---|
SyntaxError | 语法错误,如非法的 JSON、代码解析失败 |
TypeError | 类型错误,如对 null 调用方法、给只读属性赋值 |
ReferenceError | 引用错误,如访问未声明的变量 |
RangeError | 范围错误,如数组长度为负数、递归过深 |
URIError | URI 编码/解码错误 |
js
// TypeError
const obj = null;
obj.name; // TypeError: Cannot read property 'name' of null
// ReferenceError
console.log(notDefined); // ReferenceError: notDefined is not defined
// RangeError
const arr = new Array(-1); // RangeError: Invalid array length三、防御式编程
不是所有错误都需要 try/catch。对于可预判的情况,先检查再操作:
检查 null / undefined
js
// 不推荐:依赖 try/catch
function getServerName(data) {
try {
return data.server.name;
} catch {
return "未知";
}
}
// 推荐:先检查
function getServerName(data) {
if (data?.server?.name) {
return data.server.name;
}
return "未知";
}可选链 ?. 在链上任意一层为 null 或 undefined 时安全返回 undefined,比 try/catch 更简洁。
检查数组长度
js
function getFirstItem(arr) {
if (!Array.isArray(arr) || arr.length === 0) {
return null;
}
return arr[0];
}参数校验
js
function connect(host, port) {
if (typeof host !== "string" || host.length === 0) {
throw new TypeError("host 必须是非空字符串");
}
if (typeof port !== "number" || port < 1 || port > 65535) {
throw new RangeError("port 必须是 1~65535 之间的数字");
}
// ...
}四、异步错误处理
Promise 的 catch
js
fetch("/api/servers")
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((err) => {
console.error("请求失败:", err.message);
});catch 捕获链上任意一步抛出的错误。
async / await 的 try/catch
js
async function loadServers() {
try {
const response = await fetch("/api/servers");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (err) {
console.error("加载服务器列表失败:", err.message);
return []; // 返回空数组,让调用方可以继续执行
}
}await 后面如果 Promise 被拒绝(rejected),会抛出异常,需要用 try/catch 包裹。
五、全局错误捕获
window.onerror
捕获未处理的同步错误:
js
window.onerror = function (message, source, lineno, colno, error) {
console.error("全局错误:", message, "位于", `${source}:${lineno}`);
return true; // 阻止默认错误提示(控制台红字)
};unhandledrejection
捕获未处理的 Promise 拒绝:
js
window.addEventListener("unhandledrejection", (event) => {
console.error("未处理的 Promise 错误:", event.reason);
event.preventDefault();
});全局捕获是最后一道防线,不能替代具体的错误处理。生产环境可以把全局错误上报到日志平台,但每个功能模块仍应自己处理可预期的错误。
六、常见错误
吞掉所有错误
js
// 错误:catch 里什么都不做,错误被静默吞掉
try {
riskyOperation();
} catch {
// 空 catch
}
// 正确:至少记录日志,或返回错误状态
try {
riskyOperation();
} catch (err) {
console.error("操作失败:", err.message);
showErrorToUser("操作失败,请稍后重试");
}在 finally 中 return
js
function test() {
try {
return "try";
} finally {
return "finally"; // 会覆盖 try 中的返回值
}
}
console.log(test()); // "finally"finally 中的 return 会覆盖 try 和 catch 中的返回值。避免在 finally 中 return。
用 throw 控制流程
js
// 不建议:throw 不是 goto
try {
if (!user) throw new Error("未登录");
if (!permission) throw new Error("无权限");
doSomething();
} catch (err) {
handleError(err);
}
// 建议:普通条件判断
try {
if (!user) {
redirectToLogin();
return;
}
if (!permission) {
showForbidden();
return;
}
doSomething();
} catch (err) {
handleError(err);
}throw 用于真正的异常情况,正常的分支逻辑用 if/return。