Skip to content
On this page

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错误处理是构建健壮应用的关键。通过合适的错误处理策略,可以提高用户体验,便于调试和维护代码。