一、异常处理基础
1. 错误类型
JavaScript 内置了多种错误类型:
- Error:通用错误类型
- SyntaxError:语法错误
- ReferenceError:引用未定义变量
- TypeError:类型错误(如调用不存在的方法)
- RangeError:数值超出范围
- URIError:URI 处理错误
- EvalError:eval 函数错误(现代JS中很少见)
2. try-catch-finally 结构
try {
// 可能出错的代码
riskyOperation();
} catch (error) {
// 错误处理
console.error('Error occurred:', error.message);
} finally {
// 无论是否出错都会执行
cleanup();
}
3. throw 抛出异常
function validateInput(input) {
if (!input) {
throw new Error('Input cannot be empty');
}
if (input.length < 5) {
throw new RangeError('Input must be at least 5 characters');
}
}
二、实际应用场景
场景 1:API 请求错误处理
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch user:', error);
// 根据错误类型采取不同措施
if (error instanceof TypeError) {
// 网络错误或CORS问题
showNetworkErrorToast();
} else if (error.message.includes('404')) {
// 用户不存在
showUserNotFoundMessage();
} else {
// 其他错误
showGenericError();
}
// 返回默认值或重新抛出
return { name: 'Guest' };
}
}
场景 2:表单验证
function validateRegistrationForm(formData) {
try {
if (!formData.username) {
throw new Error('Username is required');
}
if (formData.password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
if (formData.age < 18) {
throw new RangeError('You must be at least 18 years old');
}
return true;
} catch (error) {
// 显示友好的错误信息给用户
displayFormError(error.message);
return false;
}
}
场景 3:JSON 解析安全处理
function safeJsonParse(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
if (error instanceof SyntaxError) {
console.warn('Invalid JSON string:', jsonString);
return null;
}
// 其他类型的错误重新抛出
throw error;
}
}
// 使用示例
const userData = safeJsonParse(localStorage.getItem('user'));
if (!userData) {
// 处理无效JSON情况
}
场景 4:异步操作中的错误处理
async function processOrder(orderId) {
try {
const order = await getOrder(orderId);
const payment = await processPayment(order);
await sendConfirmation(order, payment);
} catch (error) {
console.error('Order processing failed:', error);
// 记录错误到监控系统
logErrorToService(error);
// 尝试恢复操作
await sendFailureNotification(orderId);
// 重新抛出以便上层处理
throw new Error('Order processing failed', { cause: error });
}
}
// 调用处
processOrder(12345).catch(error => {
showErrorToUser(error.message);
});
场景 5:第三方库错误封装
class DatabaseClient {
constructor() {
this.driver = require('unstable-db-driver');
}
async query(sql) {
try {
return await this.driver.execute(sql);
} catch (error) {
// 将第三方库特定错误转换为应用标准错误
if (error.code === 'DB_CONN_TIMEOUT') {
throw new Error('Database connection timeout');
} else if (error.code === 'QUERY_SYNTAX') {
throw new SyntaxError(`Invalid SQL query: ${sql}`);
} else {
throw new Error('Database operation failed', { cause: error });
}
}
}
}
三、高级异常处理技巧
1. 自定义错误类型
class AuthenticationError extends Error {
constructor(message, metadata = {}) {
super(message);
this.name = 'AuthenticationError';
this.metadata = metadata;
this.isOperational = true; // 标记为可操作错误
}
}
// 使用
try {
login(user);
} catch (error) {
if (error instanceof AuthenticationError) {
// 专门处理认证错误
showLoginError(error.message);
} else {
throw error;
}
}
2. 错误边界(React 场景)
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
3. Promise 链中的错误处理
function fetchWithRetry(url, retries = 3) {
return fetch(url)
.catch(error => {
if (retries <= 0) throw error;
return new Promise(resolve => {
setTimeout(() => {
resolve(fetchWithRetry(url, retries - 1));
}, 1000);
});
});
}
// 使用
fetchWithRetry('/api/data')
.then(processData)
.catch(error => {
console.error('Failed after retries:', error);
});
4. Node.js 中的错误处理最佳实践
// 处理未捕获的Promise异常
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
// 处理未捕获的异常
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// 对于不可恢复的错误,最好退出进程
if (!isOperationalError(error)) {
process.exit(1);
}
});
// 区分操作错误(可恢复)和编程错误(应崩溃)
function isOperationalError(error) {
return (error instanceof BaseError && error.isOperational);
}
四、异常处理最佳实践
-
不要静默捕获所有错误:避免空的 catch 块,至少要记录错误
-
错误信息要具体:
// 不好 throw new Error('Invalid input'); // 好 throw new Error('Email address must contain @ symbol'); -
区分可恢复错误和致命错误:可恢复错误应该被捕获处理,致命错误应该记录并终止
-
错误日志要包含上下文信息:
catch (error) { console.error('Failed to process order', { error: error.message, stack: error.stack, orderId: order.id, timestamp: new Date().toISOString() }); } -
前端用户友好的错误提示:将技术性错误转换为用户能理解的信息
-
使用错误监控服务:如 Sentry、Bugsnag 等
五、实际场景总结
-
用户输入验证:提供即时反馈,防止无效数据进入系统
-
网络请求:处理超时、服务器错误、无效响应
-
第三方集成:封装不可控的外部库错误
-
关键业务流程:如支付处理,需要详细的错误日志和恢复机制
-
异步操作:Promise 和 async/await 的错误传播特性