为什么错误处理是健壮代码的基础
任何程序都会遇到错误——网络中断、用户输入无效、数据格式错误。错误处理的质量直接决定了代码的健壮性和可维护性。
这篇文章深入解析 JavaScript 的错误处理机制,从基础语法到高级模式,帮助你写出能优雅处理错误的代码。
一、Error 对象
1.1 Error 的属性
const error = new Error('Something went wrong');
console.log(error.name); // 'Error'
console.log(error.message); // 'Something went wrong'
console.log(error.stack); // 错误堆栈字符串
// 常见 Error 子类
console.log(new TypeError('Expected a string').name); // 'TypeError'
console.log(new RangeError('Value out of range').name); // 'RangeError'
console.log(new SyntaxError('Invalid syntax').name); // 'SyntaxError'
console.log(new ReferenceError('Undefined variable').name); // 'ReferenceError'
console.log(new URIError('URI error').name); // 'URIError'
1.2 Error 堆栈
function foo() {
bar();
}
function bar() {
baz();
}
function baz() {
console.log(new Error().stack);
}
foo();
/*
Error
at baz (/path/to/file.js:12:17)
at bar (/path/to/file.js:8:5)
at foo (/path/to/file.js:4:5)
at Object.<anonymous> (/path/to/file.js:15:1)
*/
二、自定义错误类
2.1 基本自定义错误
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
function validateAge(age) {
if (typeof age !== 'number') {
throw new ValidationError('Age must be a number', 'age');
}
if (age < 0 || age > 150) {
throw new ValidationError('Age must be between 0 and 150', 'age');
}
}
try {
validateAge('invalid');
} catch (e) {
if (e instanceof ValidationError) {
console.log(`Validation failed for ${e.field}: ${e.message}`);
}
}
2.2 错误类型体系
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = this.constructor.name;
this.statusCode = statusCode;
}
}
class ValidationError extends AppError {
constructor(message, field) {
super(message, 400);
this.field = field;
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404);
this.resource = resource;
}
}
class UnauthorizedError extends AppError {
constructor() {
super('Unauthorized', 401);
}
}
三、try / catch / finally
3.1 执行顺序
try {
console.log('1. try block start');
// throw new Error('test');
console.log('2. try block end'); // 如果抛出异常,这行不执行
} catch (e) {
console.log('3. catch block:', e.message);
} finally {
console.log('4. finally block'); // 始终执行
}
// 正常执行输出顺序:1 → 2 → 4
// 抛出异常输出顺序:1 → 3 → 4
3.2 catch 的块级作用域
// var 的问题
try {
throw new Error('test');
} catch (e) {
var errorMessage = e.message; // var 是函数作用域,不是块级作用域
}
console.log(errorMessage); // 'test' — 泄漏到外部
// const/let 是真正的块级作用域
try {
throw new Error('test');
} catch (e) {
const errorMessage = e.message; // 真正被捕获
}
// console.log(errorMessage); // ReferenceError
3.3 finally 与 return
function foo() {
try {
return 'try';
} finally {
console.log('finally');
}
}
// 输出:finally,然后返回 'try'
function bar() {
try {
throw new Error('test');
} catch (e) {
return 'catch';
} finally {
console.log('finally');
}
}
// 输出:finally,然后返回 'catch'
四、异步错误处理
4.1 Promise 的 .catch
fetch('/api/data')
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.catch(error => {
console.error('Error:', error.message);
});
4.2 async/await 中的 try/catch
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Network response was not ok');
return await response.json();
} catch (error) {
console.error('Fetch error:', error.message);
return null;
}
}
// 多个错误分别处理
async function handleRequests() {
const [userResult, postsResult] = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/posts')
]);
if (userResult.status === 'rejected') {
console.error('User fetch failed:', userResult.reason);
}
if (postsResult.status === 'rejected') {
console.error('Posts fetch failed:', postsResult.reason);
}
}
4.3 全局 unhandledrejection
// 未处理的 Promise 拒绝会被触发
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
});
// 防止未处理的拒绝
window.addEventListener('rejectionhandled', (event) => {
console.log('Promise rejection was handled:', event.reason);
});
五、错误处理最佳实践
5.1 不要捕获所有错误
// 错误做法:捕获所有,隐藏问题
try {
// ...
} catch (e) {
console.log('Error'); // 丢失错误信息
}
// 正确做法:只捕获能处理的错误
try {
JSON.parse(userInput);
} catch (e) {
if (e instanceof SyntaxError) {
return { error: 'Invalid JSON format' };
}
throw e; // 其他错误重新抛出
}
5.2 使用 finally 清理资源
function readFile(path) {
let file = null;
try {
file = openFile(path);
return file.read();
} finally {
if (file) {
file.close(); // 始终关闭文件
}
}
}
5.3 错误边界(Error Boundaries)
// React 错误边界示例
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logError(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}