Appearance
JavaScript 错误处理
错误处理是JavaScript开发中的重要方面,正确的错误处理能够提高应用的健壮性和用户体验。
错误类型
JavaScript有多种内置错误类型:
Error
所有错误的基类:
javascript
// 创建基本错误
const basicError = new Error('这是一个基本错误');
console.log(basicError.message); // '这是一个基本错误'
console.log(basicError.name); // 'Error'
常见错误类型
javascript
// SyntaxError - 语法错误(通常在代码解析阶段发生)
// const invalidSyntax = ; // SyntaxError: Unexpected token ';'
// ReferenceError - 引用错误
try {
console.log(undefinedVariable); // ReferenceError: undefinedVariable is not defined
} catch (error) {
console.log(error instanceof ReferenceError); // true
}
// TypeError - 类型错误
try {
null.someMethod(); // TypeError: Cannot read property 'someMethod' of null
} catch (error) {
console.log(error instanceof TypeError); // true
}
// RangeError - 范围错误
try {
const arr = new Array(-1); // RangeError: Invalid array length
} catch (error) {
console.log(error instanceof RangeError); // true
}
// URIError - URI相关错误
try {
decodeURIComponent('%'); // URIError: URI malformed
} catch (error) {
console.log(error instanceof URIError); // true
}
// EvalError - eval相关错误(很少见)
try-catch-finally 语句
基本语法
javascript
try {
// 可能出错的代码
const result = riskyOperation();
console.log('操作成功:', result);
} catch (error) {
// 错误处理
console.error('发生错误:', error.message);
} finally {
// 无论是否出错都会执行的代码
console.log('清理工作完成');
}
详细的错误信息
javascript
try {
throw new Error('自定义错误信息');
} catch (error) {
console.log('错误名称:', error.name);
console.log('错误消息:', error.message);
console.log('错误堆栈:', error.stack);
console.log('错误类型:', error.constructor.name);
}
错误对象的属性
javascript
function detailedErrorHandling() {
try {
throw new Error('详细错误示例');
} catch (error) {
// 标准属性
console.log('Message:', error.message);
console.log('Name:', error.name);
console.log('Stack:', error.stack);
// 自定义属性(如果有的话)
if (error.code) {
console.log('Code:', error.code);
}
if (error.status) {
console.log('Status:', error.status);
}
}
}
抛出错误
抛出不同类型的值
javascript
// 抛出Error对象(推荐)
function throwError() {
throw new Error('这是一个错误');
}
// 抛出字符串(不推荐)
function throwString() {
throw '这是一个字符串错误';
}
// 抛出自定义对象
function throwCustomObject() {
throw { message: '自定义错误对象', code: 500 };
}
// 检查抛出的错误类型
try {
throwCustomObject();
} catch (error) {
if (typeof error === 'object' && error.message) {
console.log('标准错误对象:', error.message);
} else if (typeof error === 'string') {
console.log('字符串错误:', error);
} else {
console.log('其他类型错误:', error);
}
}
自定义错误类型
javascript
// 创建自定义错误类
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, status) {
super(message);
this.name = 'NetworkError';
this.status = status;
}
}
// 使用自定义错误
function validateUser(user) {
if (!user.email) {
throw new ValidationError('邮箱是必需的', 'email');
}
if (!user.name) {
throw new ValidationError('姓名是必需的', 'name');
}
}
try {
validateUser({ email: '' });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`验证错误 - 字段: ${error.field}, 消息: ${error.message}`);
}
}
异步错误处理
Promise 中的错误处理
javascript
// 使用 .catch() 处理 Promise 错误
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
// 在 Promise 中抛出错误
function asyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('成功');
} else {
reject(new Error('异步操作失败'));
}
}, 1000);
});
}
asyncOperation()
.then(result => console.log(result))
.catch(error => console.error(error.message));
// Promise 链中的错误传播
Promise.resolve()
.then(() => {
throw new Error('第一个错误');
})
.then(() => {
console.log('这不会执行');
})
.catch(error => {
console.log('捕获错误:', error.message); // '第一个错误'
// 返回正常值,链继续执行
return '恢复值';
})
.then(result => {
console.log('恢复后:', result); // '恢复后: 恢复值'
});
async/await 错误处理
javascript
// 基本的 async/await 错误处理
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('获取数据失败:', error.message);
throw error; // 重新抛出,让调用者处理
}
}
// 处理多个异步操作的错误
async function fetchMultipleData() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
} catch (error) {
console.error('获取数据失败:', error.message);
// 可以返回默认值而不是抛出错误
return { users: [], posts: [], comments: [] };
}
}
并行异步操作的错误处理
javascript
// 使用 Promise.allSettled 处理并行操作,即使部分失败也继续
async function fetchMultipleDataSafe() {
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
const data = {};
const errors = [];
results.forEach((result, index) => {
const keys = ['users', 'posts', 'comments'];
if (result.status === 'fulfilled') {
data[keys[index]] = result.value;
} else {
errors.push({ key: keys[index], error: result.reason });
data[keys[index]] = null; // 设置默认值
}
});
if (errors.length > 0) {
console.warn('部分数据获取失败:', errors);
}
return data;
}
全局错误处理
window.onerror
javascript
// 捕获全局JavaScript错误
window.onerror = function(message, source, lineno, colno, error) {
console.error('全局错误:', {
message,
source,
lineno,
colno,
error
});
// 返回true表示错误已处理,不显示控制台错误信息
return true;
};
// 捕获未处理的Promise拒绝
window.addEventListener('unhandledrejection', function(event) {
console.error('未处理的Promise拒绝:', event.reason);
// 阻止默认的错误处理
event.preventDefault();
});
服务工作线程错误处理
javascript
// 在Service Worker中处理错误
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
})
.catch(error => {
console.error('请求失败:', error);
// 返回备用响应
return new Response('请求失败', { status: 500 });
})
);
});
错误处理策略
防御性编程
javascript
// 输入验证
function calculateDiscount(price, discountPercent) {
// 参数验证
if (typeof price !== 'number' || price < 0) {
throw new TypeError('价格必须是非负数');
}
if (typeof discountPercent !== 'number' || discountPercent < 0 || discountPercent > 100) {
throw new TypeError('折扣百分比必须是0-100之间的数字');
}
return price * (discountPercent / 100);
}
// 安全的属性访问
function safeGet(obj, path, defaultValue = undefined) {
try {
return path.split('.').reduce((current, key) => current[key], obj);
} catch (error) {
return defaultValue;
}
}
// 使用示例
const user = { profile: { name: 'John' } };
const name = safeGet(user, 'profile.name', 'Anonymous'); // 'John'
const email = safeGet(user, 'profile.email', 'no-email@example.com'); // 'no-email@example.com'
错误恢复机制
javascript
// 重试机制
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i <= maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} catch (error) {
if (i === maxRetries) {
throw error; // 最后一次重试失败,抛出错误
}
// 等待一段时间后重试(指数退避)
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s...
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 降级策略
async function loadFeature() {
try {
// 尝试加载高级功能
const advancedFeature = await import('./advanced-feature.js');
return advancedFeature.default();
} catch (error) {
console.warn('高级功能加载失败,使用基础功能:', error);
// 降级到基础功能
const basicFeature = await import('./basic-feature.js');
return basicFeature.default();
}
}
错误日志记录
javascript
// 错误日志服务
class ErrorLogger {
constructor(config = {}) {
this.apiUrl = config.apiUrl || '/api/logs';
this.level = config.level || 'error';
this.buffer = [];
this.flushInterval = config.flushInterval || 5000;
// 定期发送错误日志
setInterval(() => this.flush(), this.flushInterval);
}
log(error, context = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
message: error.message || error,
stack: error.stack,
context,
userAgent: navigator.userAgent,
url: window.location.href
};
this.buffer.push(logEntry);
// 如果是严重错误,立即发送
if (error.level === 'critical') {
this.flush();
}
}
async flush() {
if (this.buffer.length === 0) return;
const logs = this.buffer.splice(0);
try {
await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ logs })
});
} catch (error) {
console.error('发送错误日志失败:', error);
// 将日志放回缓冲区,稍后重试
this.buffer.unshift(...logs);
}
}
}
// 全局错误记录
const errorLogger = new ErrorLogger();
window.addEventListener('error', event => {
errorLogger.log(event.error, {
type: 'javascript-error',
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});
window.addEventListener('unhandledrejection', event => {
errorLogger.log(event.reason, {
type: 'unhandled-promise-rejection'
});
event.preventDefault(); // 阻止控制台错误
});
最佳实践
错误处理模式
javascript
// Result 模式 - 返回成功或失败的结果
class Result {
constructor(success, value, error = null) {
this.success = success;
this.value = value;
this.error = error;
}
static ok(value) {
return new Result(true, value);
}
static error(error) {
return new Result(false, null, error);
}
}
// 使用Result模式
function safeParseJSON(jsonString) {
try {
const parsed = JSON.parse(jsonString);
return Result.ok(parsed);
} catch (error) {
return Result.error(new Error(`JSON解析失败: ${error.message}`));
}
}
// 使用示例
const result = safeParseJSON('{"name": "John"}');
if (result.success) {
console.log('解析成功:', result.value);
} else {
console.error('解析失败:', result.error.message);
}
错误处理工具函数
javascript
// 统一的异步错误处理
async function safeAsync(asyncFn) {
try {
const result = await asyncFn();
return [null, result];
} catch (error) {
return [error, null];
}
}
// 使用示例
async function example() {
const [error, data] = await safeAsync(() => fetch('/api/data').then(r => r.json()));
if (error) {
console.error('请求失败:', error);
return null;
}
console.log('数据:', data);
return data;
}
// 条件错误处理
function withErrorHandling(fn, errorHandler) {
return function(...args) {
try {
return fn.apply(this, args);
} catch (error) {
if (errorHandler) {
return errorHandler(error, ...args);
} else {
console.error('未处理错误:', error);
throw error;
}
}
};
}
// 使用示例
const safeDivide = withErrorHandling(
(a, b) => {
if (b === 0) throw new Error('除零错误');
return a / b;
},
(error, a, b) => {
console.error(`计算 ${a} / ${b} 时出错:`, error.message);
return null;
}
);
console.log(safeDivide(10, 2)); // 5
console.log(safeDivide(10, 0)); // null(错误被处理)
错误分类和处理
javascript
// 错误分类处理
const ERROR_CATEGORIES = {
CLIENT: 'client', // 客户端错误(4xx)
SERVER: 'server', // 服务端错误(5xx)
NETWORK: 'network', // 网络错误
VALIDATION: 'validation', // 验证错误
TIMEOUT: 'timeout' // 超时错误
};
function categorizeError(error) {
if (error.status >= 400 && error.status < 500) {
return ERROR_CATEGORIES.CLIENT;
} else if (error.status >= 500) {
return ERROR_CATEGORIES.SERVER;
} else if (error.message.includes('NetworkError')) {
return ERROR_CATEGORIES.NETWORK;
} else if (error.message.includes('timeout')) {
return ERROR_CATEGORIES.TIMEOUT;
}
return 'unknown';
}
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
const category = categorizeError(error);
switch (category) {
case ERROR_CATEGORIES.CLIENT:
console.error('客户端错误,请检查请求参数');
break;
case ERROR_CATEGORIES.SERVER:
console.error('服务端错误,请稍后重试');
break;
case ERROR_CATEGORIES.NETWORK:
console.error('网络连接失败,请检查网络');
break;
case ERROR_CATEGORIES.TIMEOUT:
console.error('请求超时,请稍后重试');
break;
default:
console.error('未知错误:', error);
}
throw error;
}
}
JavaScript错误处理是构建健壮应用的关键。通过合适的错误处理策略,可以提高用户体验,便于调试和维护代码。