您现在的位置是:网站首页 > 不处理任何错误('fetch().then(data => use(data))')文章详情

不处理任何错误('fetch().then(data => use(data))')

直接忽略错误的后果

fetch().then(data => use(data))这种写法在前端代码中随处可见。开发者只关心成功回调,完全无视可能发生的网络错误、服务器错误或数据解析错误。当API返回500状态码时,控制台不会显示任何异常,用户界面可能显示空白或卡在加载状态。更糟糕的是,当JSON解析失败时,整个应用可能直接崩溃。

// 典型的问题代码
fetch('/api/data')
  .then(response => response.json())
  .then(data => renderDashboard(data))

错误传播的沉默中断

Promise链中的错误如果没有被捕获,会像气泡一样向上传递直到被捕获。但在这个模式中,错误永远找不到处理程序。现代浏览器会在控制台输出"Uncaught (in promise)"警告,但在生产环境中用户看不到这些信息。React 16之后,未被捕获的Promise错误甚至会导致整个组件树卸载。

// 实际发生的错误处理流程
fetch('/api') // 1. 网络错误
  .then(res => res.json()) // 2. 跳过(因为上一个Promise被reject)
  .then(data => console.log(data)) // 3. 永远不会执行

真实场景的灾难案例

电商网站的商品列表页使用这种模式加载数据。当CDN出现故障时:

  1. 用户看到无限加载动画
  2. 埋点系统收不到任何错误日志
  3. 运维无法收到监控报警
  4. 客服接到大量投诉却无法定位问题
// 实际业务中的危险代码
function loadProducts() {
  fetch('/api/products')
    .then(res => res.json())
    .then(products => {
      document.getElementById('list').innerHTML = 
        products.map(p => `<div>${p.name}</div>`).join('')
    })
}

错误处理的基础方案

最简单的修复是添加catch子句,至少将错误输出到控制台。对于关键业务操作,应该实现错误重试机制。axios等HTTP库会自动抛出非2xx状态码的错误,但fetch API需要手动处理。

// 基础错误处理
fetch('/api')
  .then(res => {
    if (!res.ok) throw new Error(res.statusText)
    return res.json()
  })
  .then(data => use(data))
  .catch(err => {
    console.error('请求失败:', err)
    showErrorToast(err.message)
  })

高级错误处理模式

对于生产环境应用,应该实现分层的错误处理策略:

  1. 网络层:处理CORS、超时、离线状态
  2. HTTP层:处理403、404、500等状态码
  3. 业务层:处理数据格式错误、空数据
  4. UI层:显示友好的错误提示
// 分层错误处理示例
async function loadUserProfile(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`, {
      signal: AbortSignal.timeout(5000)
    });
    
    if (response.status === 404) {
      return navigate('/not-found')
    }
    
    const data = await response.json();
    
    if (!data.profile) {
      throw new Error('Invalid profile data')
    }
    
    return renderProfile(data);
  } catch (error) {
    if (error.name === 'AbortError') {
      showToast('请求超时,请重试')
    } else {
      logErrorToService(error)
      showErrorPage(error)
    }
  }
}

框架中的最佳实践

现代前端框架提供了更好的错误处理机制。React Error Boundary可以捕获组件树中的错误,Vue的errorCaptured钩子也有类似功能。结合这些机制可以构建更健壮的应用。

// React错误边界示例
class ErrorBoundary extends React.Component {
  state = { hasError: false }
  
  static getDerivedStateFromError() {
    return { hasError: true }
  }
  
  componentDidCatch(error, info) {
    logErrorToService(error, info.componentStack)
  }
  
  render() {
    return this.state.hasError
      ? <FallbackUI />
      : this.props.children
  }
}

// 使用方式
<ErrorBoundary>
  <UserProfile />
</ErrorBoundary>

监控与日志记录

仅仅捕获错误还不够,还需要建立完整的监控体系。Sentry、Bugsnag等工具可以捕获客户端错误并关联用户会话信息。对于关键路径操作,应该记录完整的错误上下文。

// 使用Sentry记录错误
import * as Sentry from '@sentry/browser'

fetch('/api')
  .then(processResponse)
  .catch(error => {
    Sentry.captureException(error, {
      contexts: {
        request: {
          url: '/api',
          method: 'GET'
        }
      }
    })
  })

用户体验的考量

错误处理不仅是技术问题,更是用户体验问题。不同类型的错误需要不同的处理方式:

  • 网络错误:提供重试按钮
  • 权限错误:引导到登录页
  • 服务器错误:显示维护页面
  • 数据错误:展示降级UI
// 根据错误类型显示不同UI
function handleError(error) {
  if (error instanceof NetworkError) {
    return <NetworkErrorModal onRetry={fetchData} />
  }
  if (error instanceof AuthError) {
    return <Redirect to="/login" />
  }
  return <GenericError message={error.message} />
}

测试策略的调整

完整的错误处理需要相应的测试覆盖。除了测试正常流程,还应该专门测试各种错误场景。Jest等测试框架支持测试异步错误。

// 测试错误处理的示例
test('handles 404 errors', async () => {
  fetch.mockResponseOnce('', { status: 404 })
  
  const { getByText } = render(<UserPage />)
  await waitFor(() => {
    expect(getByText('User not found')).toBeInTheDocument()
  })
})

TypeScript的增强保护

TypeScript可以在编译时帮助发现一些潜在的错误处理问题。通过配置strictNullChecks和自定义类型保护,可以强制处理可能的空值或错误状态。

interface ApiResponse<T> {
  data?: T
  error?: {
    code: number
    message: string
  }
}

async function fetchData<T>(url: string): Promise<T> {
  const response: ApiResponse<T> = await fetch(url).then(r => r.json())
  
  if (response.error) {
    throw new Error(response.error.message)
  }
  
  if (!response.data) {
    throw new Error('Empty response')
  }
  
  return response.data
}

性能与错误处理的平衡

过度防御的错误处理可能影响性能。关键是要在关键路径上进行必要的检查,同时避免过多的try-catch块影响V8的优化。对于非关键操作,可以考虑延迟错误处理。

// 性能优化的错误处理
function parseJSONSafely(json) {
  // 使用函数隔离try-catch避免影响外层性能
  try {
    return JSON.parse(json)
  } catch {
    return null
  }
}

async function loadConfig() {
  const response = await fetch('/config.json')
  const text = await response.text()
  return parseJSONSafely(text) || loadDefaultConfig()
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步