error-handling-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Error Handling Patterns

错误处理模式

Overview

概述

Error handling is not only about preventing crashes. It is about deciding:
  • what the user should see
  • what can be retried
  • what state should be preserved
  • what should be logged or reported
  • when the system should fail fast vs degrade gracefully
Good error handling makes failures understandable, containable, and recoverable.
错误处理不仅是防止崩溃,更要决定:
  • 用户应该看到什么
  • 哪些操作可以重试
  • 应该保留哪些状态
  • 哪些内容需要记录或上报
  • 系统何时应该快速失败,何时应该优雅降级
良好的错误处理能让故障变得可理解、可控制且可恢复。

Types of Errors

错误类型

Different failures need different handling.
Error TypeExampleTypical Response
ValidationInvalid form inputInline correction guidance
NetworkTimeout, offline, DNS failureRetry, offline fallback, preserve user intent
Authorization401, 403Re-auth, permission message, redirect
Server500, dependency outageRetry or graceful fallback
Not foundMissing route or deleted recordNot-found UI, navigation recovery
ConflictStale write, version mismatchRefresh, merge, or ask user to resolve
RenderingComponent crashError boundary, fallback UI
Background failurePolling or refresh failureNon-blocking notification, degraded data state
Treating all failures the same leads to confusing UI and bad recovery behavior.
不同的故障需要不同的处理方式。
错误类型示例典型响应
验证错误无效表单输入内联修正指引
网络错误超时、离线、DNS失败重试、离线回退、保留用户操作意图
授权错误401、403重新授权、权限提示、重定向
服务器错误500、依赖服务中断重试或优雅回退
未找到错误路由缺失或记录已删除未找到UI、导航恢复
冲突错误过时写入、版本不匹配刷新、合并或请求用户解决
渲染错误组件崩溃Error Boundaries、回退UI
后台故障轮询或刷新失败非阻塞通知、降级数据状态
对所有故障一视同仁会导致UI混乱和糟糕的恢复体验。

Error Handling Goals

错误处理目标

Good systems try to:
  • avoid unnecessary crashes
  • preserve user work when possible
  • explain the problem in actionable language
  • offer the next best recovery path
  • capture enough telemetry to diagnose the issue later
优秀的系统会尝试:
  • 避免不必要的崩溃
  • 尽可能保留用户工作内容
  • 用可执行的语言解释问题
  • 提供最优的恢复路径
  • 捕获足够的遥测数据以便后续诊断问题

Layered Error Handling

分层错误处理

Handle errors at the right layer.
在合适的层级处理错误。

Component / Inline Layer

组件/内联层级

Good for:
  • form validation
  • failed mutation on one record
  • retrying a single action
适用于:
  • 表单验证
  • 单条记录的突变失败
  • 单个操作的重试

Page / Route Layer

页面/路由层级

Good for:
  • failed page data load
  • access-denied or not-found states
  • route-specific crash recovery
适用于:
  • 页面数据加载失败
  • 访问被拒绝或未找到状态
  • 路由特定的崩溃恢复

Application / Global Layer

应用/全局层级

Good for:
  • uncaught rendering crashes
  • catastrophic boot failures
  • unexpected fatal conditions
适用于:
  • 未捕获的渲染崩溃
  • 灾难性启动失败
  • 意外的致命条件

Service / Backend Layer

服务/后端层级

Good for:
  • standardizing error responses
  • mapping dependency errors
  • adding request IDs and diagnostics
Not every error should bubble to the top of the app. Most should be handled close to the failing task.
适用于:
  • 标准化错误响应
  • 映射依赖服务错误
  • 添加请求ID和诊断信息
并非所有错误都需要冒泡到应用顶层,大多数错误应该在靠近故障任务的位置处理。

Normalize Error Shapes

标准化错误格式

Applications become easier to handle when errors are mapped into a consistent shape.
javascript
{
  code: 'NETWORK_TIMEOUT',
  message: 'Request timed out',
  retryable: true,
  statusCode: 504,
  requestId: 'req_123'
}
Useful normalized fields:
  • machine-readable code
  • human-readable message
  • retryability
  • status or category
  • request or trace ID
Without normalization, each component has to guess how to interpret failures.
当错误被映射为统一格式时,应用会更易于处理。
javascript
{
  code: 'NETWORK_TIMEOUT',
  message: 'Request timed out',
  retryable: true,
  statusCode: 504,
  requestId: 'req_123'
}
有用的标准化字段:
  • 机器可读的代码
  • 人类可读的消息
  • 是否可重试
  • 状态或类别
  • 请求或追踪ID
如果没有标准化,每个组件都需要猜测如何解读故障。

User-Facing Error UX

用户可见的错误UX

The UI should communicate what happened and what the user can do next.
UI应告知用户发生了什么,以及他们接下来可以做什么。

Good Error Messages

良好的错误消息

  • Say what failed
  • Say whether the user can retry or fix something
  • Avoid internal jargon
  • Preserve technical detail for logs, not for primary UI
Bad:
text
Unknown exception occurred
Better:
text
We couldn’t save your changes. Check your connection and try again.
  • 说明什么操作失败了
  • 说明用户是否可以重试或修复问题
  • 避免内部术语
  • 技术细节保留在日志中,而非主UI展示
糟糕的示例:
text
Unknown exception occurred
更好的示例:
text
我们无法保存您的更改。请检查网络连接后重试。

Inline vs Global Errors

内联错误 vs 全局错误

UI PlacementBest For
Inline near fieldValidation and input-specific errors
Inline near componentFailed widget or card action
Page-level messagePage data load failure
ToastSecondary status updates, non-blocking failures
Full-screen fallbackFatal route or app crash
If the failure affects only one action, keep the error near that action.
UI位置适用场景
字段附近内联验证和特定输入错误
组件附近内联部件或卡片操作失败
页面级消息页面数据加载失败
提示弹窗(Toast)次要状态更新、非阻塞故障
全屏回退致命路由或应用崩溃
如果故障仅影响单个操作,应将错误保持在该操作附近。

Preserve User Intent

保留用户操作意图

Failure handling is better when users do not lose work.
Examples:
  • keep form values after failed submit
  • keep draft comment text after network failure
  • keep optimistic state recoverable when server rejects it
  • preserve navigation context when auth expires
Losing work turns a recoverable failure into a frustrating experience.
当用户不会丢失工作内容时,故障处理体验会更好。
示例:
  • 提交失败后保留表单值
  • 网络故障后保留草稿评论文本
  • 服务器拒绝时保持乐观状态可恢复
  • 授权过期时保留导航上下文
丢失工作内容会将可恢复的故障转化为令人沮丧的体验。

Retry Patterns

重试模式

Retries are useful for transient failures, not permanent ones.
Good retry candidates:
  • network timeouts
  • temporary 5xx errors
  • flaky third-party dependency calls
Bad retry candidates:
  • validation errors
  • permission errors
  • malformed requests
重试适用于临时故障,而非永久性故障。
适合重试的场景:
  • 网络超时
  • 临时5xx错误
  • 不稳定的第三方依赖调用
不适合重试的场景:
  • 验证错误
  • 权限错误
  • 格式错误的请求

Backoff Pattern

退避模式

javascript
for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await request();
  } catch (error) {
    if (!isRetryable(error)) throw error;
    await wait(2 ** attempt * 500);
  }
}
Retries should usually use backoff and a cap. Blind aggressive retries can amplify outages.
javascript
for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await request();
  } catch (error) {
    if (!isRetryable(error)) throw error;
    await wait(2 ** attempt * 500);
  }
}
重试通常应使用退避策略并设置上限。盲目激进的重试可能会加剧故障。

Idempotency and Safe Recovery

幂等性与安全恢复

Retries are much safer when the backend supports idempotent operations.
Examples:
  • retrying a GET is usually safe
  • retrying “create payment” may not be safe without an idempotency key
  • retrying “save draft” is safer when updates replace state rather than append state
Frontend recovery design is stronger when backend semantics are predictable.
当后端支持幂等操作时,重试会安全得多。
示例:
  • 重试GET请求通常是安全的
  • 没有幂等键的情况下,重试“创建支付”可能不安全
  • 当更新是替换状态而非追加状态时,重试“保存草稿”更安全
当后端语义可预测时,前端恢复设计会更健壮。

Error Boundaries

Error Boundaries

Rendering crashes should not take down the whole application if they can be contained.
Error boundaries are useful for:
  • route-level fallback UI
  • isolating risky widgets
  • keeping the shell usable when one region fails
如果可以控制,渲染崩溃不应导致整个应用瘫痪。
Error Boundaries适用于:
  • 路由级回退UI
  • 隔离风险部件
  • 当某个区域故障时保持应用框架可用

Conceptual Example

概念示例

jsx
<ErrorBoundary fallback={<WidgetError />}>
  <RevenueChart />
</ErrorBoundary>
Important limitation:
  • rendering boundaries do not catch every async error automatically
  • async failures in effects, requests, or event handlers still need explicit handling
jsx
<ErrorBoundary fallback={<WidgetError />}>
  <RevenueChart />
</ErrorBoundary>
重要限制:
  • 渲染边界不会自动捕获所有异步错误
  • 副作用、请求或事件处理程序中的异步故障仍需要显式处理

Async Error States

异步错误状态

Many application failures happen in async flows:
  • page load
  • search
  • mutation submit
  • background refresh
  • realtime reconnect
These need explicit UI states:
  • loading
  • success
  • empty
  • error
  • retrying
  • stale or degraded
If those states are not modeled directly, async errors usually leak into inconsistent UI.
许多应用故障发生在异步流程中:
  • 页面加载
  • 搜索
  • 突变提交
  • 后台刷新
  • 实时重连
这些需要显式的UI状态:
  • 加载中
  • 成功
  • 空状态
  • 错误
  • 重试中
  • 过期或降级
如果这些状态没有被直接建模,异步错误通常会导致UI不一致。

Graceful Degradation

优雅降级

Sometimes the best response is not full failure but reduced capability.
Examples:
  • show cached data while refresh fails
  • disable one advanced widget while core page remains usable
  • fall back from realtime updates to polling
  • serve static or stale content during partial backend outage
Graceful degradation is valuable when:
  • the core task can still continue
  • the degraded behavior is honest and understandable
有时最好的响应不是完全故障,而是降低功能。
示例:
  • 刷新失败时显示缓存数据
  • 禁用一个高级部件,同时保持核心页面可用
  • 从实时更新回退到轮询
  • 部分后端故障时提供静态或过期内容
优雅降级在以下场景很有价值:
  • 核心任务仍可继续
  • 降级行为清晰易懂

Page Load Error Patterns

页面加载错误模式

Full-Page Failure

全页故障

Use when the entire route depends on one failed resource.
Typical options:
  • retry button
  • back navigation
  • support link or status page
适用于整个路由依赖于某个失败资源的情况。
典型选项:
  • 重试按钮
  • 返回导航
  • 支持链接或状态页面

Partial Failure

部分故障

Use when one page section failed but the rest still works.
Typical options:
  • failed widget fallback
  • per-section retry
  • degraded view with timestamp or stale indicator
Partial failures are often better than blank screens.
适用于页面某个区域故障但其余部分仍可用的情况。
典型选项:
  • 故障部件回退
  • 按区域重试
  • 带时间戳或过期标识的降级视图
部分故障通常比空白屏幕更好。

Mutation Error Patterns

突变错误模式

Mutations need careful handling because user intent is active and recent.
Common rules:
  • keep input or draft data
  • show the error close to the failed action
  • allow retry when appropriate
  • roll back optimistic state if needed
  • do not silently fail
Example:
javascript
try {
  await saveProfile(input);
  setStatus('saved');
} catch (error) {
  setStatus('error');
  setErrorMessage(toUserMessage(error));
}
突变需要谨慎处理,因为用户的操作意图是活跃且近期的。
常见规则:
  • 保留输入或草稿数据
  • 在失败操作附近显示错误
  • 适当时允许重试
  • 需要时回滚乐观状态
  • 不要静默失败
示例:
javascript
try {
  await saveProfile(input);
  setStatus('saved');
} catch (error) {
  setStatus('error');
  setErrorMessage(toUserMessage(error));
}

Auth and Permission Errors

授权与权限错误

Auth failures need different handling from generic server errors.
Examples:
  • 401
    : session expired, needs login refresh
  • 403
    : user authenticated but not allowed
Recommended responses:
  • preserve current location when redirecting to login
  • show permission errors clearly
  • avoid looping redirects on expired sessions
授权故障需要与通用服务器错误不同的处理方式。
示例:
  • 401
    :会话过期,需要刷新登录
  • 403
    :用户已认证但无权限
推荐响应:
  • 重定向到登录时保留当前位置
  • 清晰显示权限错误
  • 避免会话过期时的循环重定向

Accessibility in Error Handling

错误处理中的可访问性

Errors should be perceivable and navigable.
Important patterns:
  • move focus to error summary or first invalid field after submit
  • announce dynamic errors with appropriate live regions when focus does not move
  • do not rely on color alone
  • keep messages visible long enough to read
Accessible error handling is part of functional correctness, not just polish.
错误应可感知且可导航。
重要模式:
  • 提交后将焦点移至错误摘要或第一个无效字段
  • 当焦点未移动时,用合适的实时区域播报动态错误
  • 不要仅依赖颜色
  • 保持消息可见足够长的时间以便阅读
可访问的错误处理是功能正确性的一部分,而非仅仅是优化。

Observability Hooks

可观测性钩子

Not every user-facing error should become a noisy alert, but important failures should be observable.
Useful data to capture:
  • error code
  • route or feature
  • release version
  • request or trace ID
  • retry count
  • whether the error reached the user
This helps distinguish:
  • noisy internal events
  • actual user-impacting failures
并非所有用户可见的错误都应成为嘈杂的警报,但重要的故障应该是可观测的。
需要捕获的有用数据:
  • 错误代码
  • 路由或功能
  • 版本号
  • 请求或追踪ID
  • 重试次数
  • 错误是否传达给用户
这有助于区分:
  • 嘈杂的内部事件
  • 实际影响用户的故障

Common Anti-Patterns

常见反模式

Anti-PatternWhy It Hurts
Generic “Something went wrong” everywhereGives no recovery path
Swallowing errors silentlyFails without user or developer visibility
Retrying non-retryable failuresWastes time and increases confusion
Clearing user input on failed submitLoses work
Treating empty state as errorProduces wrong UX
Full-page crash for one failed widgetOver-scopes the failure
反模式危害
所有地方都用通用的“出了点问题”没有提供恢复路径
静默吞噬错误用户和开发者都无法感知故障
重试不可重试的故障浪费时间并增加混乱
提交失败时清除用户输入丢失工作内容
将空状态视为错误产生错误的UX
单个故障部件导致全页崩溃过度扩大故障范围

Decision Guide

决策指南

Use this rough mapping:
  • Invalid user input: inline validation and correction
  • Failed data load: route fallback or section fallback with retry
  • Failed mutation: preserve intent, show local error, retry if safe
  • Expired session: re-auth flow with context preservation
  • Uncaught render crash: error boundary
  • Dependency degradation: graceful fallback plus telemetry
使用以下大致映射:
  • 无效用户输入:内联验证和修正
  • 数据加载失败:路由回退或区域回退+重试
  • 突变失败:保留操作意图,显示本地错误,安全时重试
  • 会话过期:保留上下文的重新授权流程
  • 未捕获的渲染崩溃:Error Boundaries
  • 依赖服务降级:优雅回退+遥测

Rules of Thumb

经验法则

  • Handle failures as close to the action as possible
  • Preserve user work whenever you can
  • Retry only when the failure is likely transient
  • Standardize error shapes early
  • Distinguish user-fixable from system-fixable problems
  • Connect user-visible failures to observability data
  • 尽可能在靠近操作的位置处理故障
  • 尽可能保留用户工作内容
  • 仅当故障可能是临时的时才重试
  • 尽早标准化错误格式
  • 区分用户可修复和系统可修复的问题
  • 将用户可见的故障与可观测数据关联

Related Skills

相关技能

  • observability-patterns
    for telemetry, alerts, and production diagnosis
  • accessibility-interaction-patterns
    for perceivable and navigable error feedback
  • optimistic-ui-patterns
    for rollback and mutation recovery
  • data-fetching-patterns
    for load, stale, and retry flows
  • observability-patterns
    :遥测、警报和生产环境诊断
  • accessibility-interaction-patterns
    :可感知且可导航的错误反馈
  • optimistic-ui-patterns
    :回滚和突变恢复
  • data-fetching-patterns
    :加载、过期和重试流程