error-handling-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseError 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 Type | Example | Typical Response |
|---|---|---|
| Validation | Invalid form input | Inline correction guidance |
| Network | Timeout, offline, DNS failure | Retry, offline fallback, preserve user intent |
| Authorization | 401, 403 | Re-auth, permission message, redirect |
| Server | 500, dependency outage | Retry or graceful fallback |
| Not found | Missing route or deleted record | Not-found UI, navigation recovery |
| Conflict | Stale write, version mismatch | Refresh, merge, or ask user to resolve |
| Rendering | Component crash | Error boundary, fallback UI |
| Background failure | Polling or refresh failure | Non-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 occurredBetter:
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 Placement | Best For |
|---|---|
| Inline near field | Validation and input-specific errors |
| Inline near component | Failed widget or card action |
| Page-level message | Page data load failure |
| Toast | Secondary status updates, non-blocking failures |
| Full-screen fallback | Fatal 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:
- : session expired, needs login refresh
401 - : user authenticated but not allowed
403
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-Pattern | Why It Hurts |
|---|---|
| Generic “Something went wrong” everywhere | Gives no recovery path |
| Swallowing errors silently | Fails without user or developer visibility |
| Retrying non-retryable failures | Wastes time and increases confusion |
| Clearing user input on failed submit | Loses work |
| Treating empty state as error | Produces wrong UX |
| Full-page crash for one failed widget | Over-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
相关技能
- for telemetry, alerts, and production diagnosis
observability-patterns - for perceivable and navigable error feedback
accessibility-interaction-patterns - for rollback and mutation recovery
optimistic-ui-patterns - for load, stale, and retry flows
data-fetching-patterns
- :遥测、警报和生产环境诊断
observability-patterns - :可感知且可导航的错误反馈
accessibility-interaction-patterns - :回滚和突变恢复
optimistic-ui-patterns - :加载、过期和重试流程
data-fetching-patterns