---
url: /article/l9mhrvdi/index.md
---
在 JavaScript 开发中，错误处理是构建健壮应用的关键技能。本文将深入探讨 JavaScript 错误处理机制，从基础语法到高级实践，帮助您写出更可靠的代码。

## 为什么需要错误处理？

:::warning 错误不可避免
在真实的软件环境中，错误是不可避免的。好的代码不仅在一切顺利时能够正常工作，而且在出现问题时也能保持可预测性和安全性。
:::

JavaScript 中的错误主要分为几类：

* **语法错误**：代码编写不规范，在解析阶段就会报错
* **运行时错误**：代码执行过程中出现的错误
* **逻辑错误**：代码逻辑有误，但不会抛出异常

```javascript title="常见错误示例"
// 1. 引用错误
console.log(undefinedVariable) // ReferenceError

// 2. 类型错误
null.function() // TypeError

// 3. 语法错误（无法通过 try...catch 捕获）
// console.log("hello; // SyntaxError
```

## Error 对象详解

当错误发生时，JavaScript 会创建一个 Error 对象，包含以下重要属性：

```javascript
try {
  let user = undefinedUser
}
catch (error) {
  console.log('错误名称:', error.name) // ReferenceError
  console.log('错误信息:', error.message) // undefinedUser is not defined
  console.log('调用栈:', error.stack) // 详细的调用栈信息
}
```

:::info Error 对象属性

* `name`：错误类型名称
* `message`：错误描述信息
* `stack`：调用栈信息（调试用）
  :::

## try...catch 基础

### 基本语法

```javascript
try {
  // 可能出错的代码
  riskyOperation()
}
catch (error) {
  // 错误处理逻辑
  console.error('操作失败:', error.message)
}
```

### 实际应用示例

:::: demo normal title="JSON 解析错误处理" desc="处理可能格式错误的 JSON 数据"
::: code-tabs
@tab HTML

```html
<div id="result"></div>
<button onclick="parseJSON()">解析 JSON</button>
```

@tab JavaScript

```javascript
function parseJSON() {
  const jsonString = '{"name": "Alice", "age": 25}' // 有效的 JSON
  // const jsonString = '{"name": Alice}'; // 无效的 JSON

  try {
    const user = JSON.parse(jsonString)
    document.getElementById('result').innerHTML
      = `姓名: ${user.name}, 年龄: ${user.age}`
  }
  catch (error) {
    document.getElementById('result').innerHTML
      = `JSON 解析失败: ${error.message}`
  }
}
```

:::
::::

### try...catch 的限制

:::caution 重要限制

1. **仅对运行时错误有效**：语法错误无法捕获
2. **同步工作**：无法直接捕获异步操作中的错误
   :::

```javascript title="异步错误无法直接捕获"
try {
  setTimeout(() => {
    throw new Error('异步错误') // 这个错误无法被外部的 catch 捕获
  }, 1000)
}
catch (error) {
  console.log('这行代码不会执行')
}
```

## throw 操作符与自定义错误

### 基本用法

`throw` 语句允许您创建自定义错误：

```javascript
function validateAge(age) {
  if (age < 0) {
    throw new Error('年龄不能为负数')
  }
  if (age > 150) {
    throw new Error('年龄超出合理范围')
  }
  return true
}

try {
  validateAge(-5)
}
catch (error) {
  console.error('验证失败:', error.message)
}
```

### 自定义错误类型

通过继承 Error 类创建更具体的错误类型：

```javascript title="自定义验证错误"
class ValidationError extends Error {
  constructor(field, message) {
    super(`${field} 验证失败: ${message}`)
    this.name = 'ValidationError'
    this.field = field
    this.timestamp = new Date().toISOString()
  }
}

class NetworkError extends Error {
  constructor(url, status) {
    super(`请求 ${url} 失败，状态码: ${status}`)
    this.name = 'NetworkError'
    this.status = status
  }
}

// 使用示例
function registerUser(userData) {
  if (!userData.username) {
    throw new ValidationError('username', '用户名不能为空')
  }
  if (userData.password.length < 8) {
    throw new ValidationError('password', '密码至少8位')
  }

  // 模拟网络请求
  throw new NetworkError('/api/register', 500)
}

try {
  registerUser({ username: 'john', password: '123' })
}
catch (error) {
  if (error instanceof ValidationError) {
    console.error('输入验证错误:', error.message)
  }
  else if (error instanceof NetworkError) {
    console.error('网络错误:', error.message)
  }
  else {
    console.error('未知错误:', error.message)
  }
}
```

## finally 块的作用

`finally` 块中的代码==总是会执行=={.important}，无论是否发生错误：

```javascript
function processFile(filename) {
  let fileHandle = null

  try {
    console.log(`开始处理文件: ${filename}`)
    fileHandle = `handle_${filename}` // 模拟文件打开
    // 模拟文件处理可能出错
    if (Math.random() > 0.5) {
      throw new Error('文件处理失败')
    }
    console.log('文件处理成功')
  }
  catch (error) {
    console.error('处理过程中出错:', error.message)
    throw error // 重新抛出错误
  }
  finally {
    // 无论成功还是失败，都要关闭文件
    console.log(`关闭文件句柄: ${fileHandle}`)
    fileHandle = null
  }
}

try {
  processFile('data.txt')
}
catch (error) {
  console.log('外部捕获:', error.message)
}
```

## 异步错误处理

### Promise 错误处理

```javascript
// .catch() 方法
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('请求失败:', error.message))

// 或者使用 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 // 可以选择重新抛出
  }
}
```

### 异步函数中的错误处理模式

```javascript title="健壮的异步错误处理"
class ApiService {
  async request(url, options = {}) {
    try {
      const response = await fetch(url, {
        timeout: 5000,
        ...options
      })

      if (!response.ok) {
        throw new Error(`请求失败: ${response.status}`)
      }

      return await response.json()
    }
    catch (error) {
      // 记录错误日志
      console.error(`API请求错误 [${url}]:`, error.message)

      // 根据错误类型提供友好的错误信息
      if (error.name === 'TypeError' && error.message.includes('fetch')) {
        throw new Error('网络连接失败，请检查网络设置')
      }

      throw error
    }
  }
}

// 使用示例
const api = new ApiService()

async function loadUserData(userId) {
  try {
    const user = await api.request(`/api/users/${userId}`)
    const posts = await api.request(`/api/users/${userId}/posts`)

    return { user, posts }
  }
  catch (error) {
    console.error('加载用户数据失败:', error.message)
    // 可以在这里显示用户友好的错误信息
    return null
  }
}
```

## 最佳实践与设计模式

### 1. 错误传播与重新抛出

:::tip 重新抛出原则
只处理你能处理的错误，将其他错误传递给上层调用者。
:::

```javascript
function processUserData(userData) {
  try {
    // 数据验证
    if (!userData.email.includes('@')) {
      throw new ValidationError('email', '邮箱格式不正确')
    }

    // 业务逻辑处理
    const result = complexBusinessLogic(userData)
    return result
  }
  catch (error) {
    // 只处理验证错误，其他错误重新抛出
    if (error instanceof ValidationError) {
      console.warn('输入验证警告:', error.message)
      return { success: false, error: error.message }
    }

    // 重新抛出未知错误
    throw error
  }
}

// 上层调用
try {
  const result = processUserData({ email: 'invalid-email' })
  console.log(result)
}
catch (error) {
  // 这里会捕获到除 ValidationError 外的所有错误
  console.error('严重错误:', error.message)
}
```

### 2. 全局错误处理

```javascript
// 全局错误捕获
window.addEventListener('error', (event) => {
  console.error('全局错误:', event.error)
  // 可以在这里发送错误报告到服务器
  sendErrorReport(event.error)
})

// 未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
  console.error('未处理的 Promise 拒绝:', event.reason)
  event.preventDefault() // 防止默认的错误输出
})

// 错误报告函数
async function sendErrorReport(error) {
  const report = {
    message: error.message,
    stack: error.stack,
    url: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: new Date().toISOString()
  }

  try {
    await fetch('/api/error-report', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(report)
    })
  }
  catch (reportError) {
    console.warn('错误报告发送失败:', reportError.message)
  }
}
```

### 3. 防御性编程模式

```javascript title="防御性函数设计"
class DataProcessor {
  // 带有完整错误处理的处理函数
  async processWithRetry(operation, maxRetries = 3) {
    let lastError

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        console.log(`尝试第 ${attempt} 次执行...`)
        const result = await operation()
        console.log('操作成功')
        return result
      }
      catch (error) {
        lastError = error
        console.warn(`第 ${attempt} 次尝试失败:`, error.message)

        if (attempt < maxRetries) {
          // 指数退避
          const delay = Math.min(1000 * 2 ** (attempt - 1), 30000)
          console.log(`等待 ${delay}ms 后重试...`)
          await new Promise(resolve => setTimeout(resolve, delay))
        }
      }
    }

    throw new Error(`操作在 ${maxRetries} 次尝试后失败，最后错误: ${lastError.message}`)
  }

  // 安全的数据访问
  getSafe(obj, path, defaultValue = null) {
    try {
      const value = path.split('.').reduce((current, key) => {
        return current && current[key] !== undefined ? current[key] : undefined
      }, obj)

      return value !== undefined ? value : defaultValue
    }
    catch (error) {
      console.warn(`安全访问路径 ${path} 失败:`, error.message)
      return defaultValue
    }
  }
}

// 使用示例
const processor = new DataProcessor()

// 带重试的操作
processor.processWithRetry(async () => {
  const response = await fetch('/api/unstable-endpoint')
  if (!response.ok)
    throw new Error(`HTTP ${response.status}`)
  return response.json()
}).then(data => console.log('最终结果:', data)).catch(error => console.error('所有重试都失败了:', error.message))

// 安全数据访问
const user = { profile: { address: { city: '北京' } } }
const city = processor.getSafe(user, 'profile.address.city', '未知')
const zipCode = processor.getSafe(user, 'profile.address.zipCode', '000000')
```

## 总结

1. **==始终处理可预见的错误=={.success}**：不要忽略你知道可能发生的错误
2. **==使用适当的错误类型=={.info}**：创建有意义的自定义错误类
3. **==异步错误特殊处理=={.warning}**：Promise 和 async/await 需要专门的错误处理
4. **==合理使用 finally=={.tip}**：确保资源清理和状态重置
5. **==实现全局错误处理=={.important}**：捕获未处理的错误并提供用户友好的反馈

通过掌握这些错误处理技术，您将能够构建更加健壮、可靠的 JavaScript 应用程序。记住，好的错误处理不仅能防止应用崩溃，还能提供更好的用户体验和更快的故障排查。

## 参考

* [MDN Error 文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error)
* [Promise 错误处理最佳实践](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises)
* [Node.js 错误处理模式](https://nodejs.org/api/errors.html)
