name: 错误处理基础 description: 指导异步操作和API调用的错误处理。当新手询问"如果失败怎么办"、“处理错误”、“try catch”、“网络错误”,或使用fetch、promises或外部服务构建功能时使用。
错误处理基础回顾
“错误不是失败——它们是信息。像对待有价值的数据一样处理它们。”
何时应用
在审查以下内容时激活此技能:
- try/catch块
- Promise链(.then/.catch)
- API错误响应
- 表单验证
- 网络请求处理
- 面向用户的错误消息
审查清单
错误捕获
- [ ] 没有空捕获:每个catch块是否都执行有意义的操作?
- [ ] 正确的作用域:错误是否在适当的级别被捕获?
- [ ] 添加上下文:错误是否包含有用的调试信息?
- [ ] 使用finally:清理操作是否放在finally块中?
用户体验
- [ ] 用户友好的消息:用户能理解错误吗?
- [ ] 不暴露技术细节:堆栈跟踪是否对用户隐藏?
- [ ] 可操作的反馈:错误是否告诉用户下一步该做什么?
- [ ] 优雅降级:在错误时,应用是否仍能部分工作?
日志记录与调试
- [ ] 错误已记录:错误是否记录用于调试?
- [ ] 日志中的上下文:日志是否包含请求ID、用户ID等?
- [ ] 严重性级别:关键错误是否与警告区分?
恢复
- [ ] 重试逻辑:此操作失败时是否应该重试?
- [ ] 回退值:错误时是否有合理的默认值?
- [ ] 加载状态:在重试时是否通知用户?
常见错误(反模式)
1. 空捕获(沉默的杀手)
❌ try {
await submitForm();
} catch (error) {
// TODO: 稍后处理(永远不会发生)
}
✅ try {
await submitForm();
} catch (error) {
console.error('表单提交失败:', error);
setError('无法提交。请重试。');
}
2. 捕获过早
❌ function getUser(id) {
try {
return database.query(id);
} catch {
return null; // 调用者不知道失败了
}
}
✅ function getUser(id) {
return database.query(id); // 让调用者决定
}
// 在UI层
try {
const user = await getUser(id);
} catch (error) {
showToast('无法加载用户');
}
3. 通用错误消息
❌ catch (error) {
setError('发生错误');
}
✅ catch (error) {
if (error.code === 'NETWORK_ERROR') {
setError('检查您的互联网连接');
} else if (error.code === 'NOT_FOUND') {
setError('用户未找到');
} else {
setError('出了点问题。请重试。');
}
}
4. 泄露堆栈跟踪
❌ res.status(500).json({
error: error.message,
stack: error.stack
});
✅ logger.error('请求失败', { error, requestId });
res.status(500).json({
error: '出了点问题'
});
5. 忘记Finally
❌ try {
setLoading(true);
await fetchData();
setLoading(false);
} catch (error) {
handleError(error);
// 加载状态永远为真!
}
✅ try {
setLoading(true);
await fetchData();
} catch (error) {
handleError(error);
} finally {
setLoading(false); // 始终运行
}
苏格拉底式问题
向新手询问这些问题,而不是直接给出答案:
- 空捕获:“如果这悄无声息地失败,会发生什么?”
- 用户消息:“如果你是用户,这条消息会帮助您吗?”
- 恢复:“我们应该自动重试这个吗?”
- 作用域:“这是捕获此错误的合适地方吗?”
- 日志记录:“您将如何在生产环境中调试这个?”
错误消息指南
应该
- 具体:“电子邮件地址已被注册”
- 有帮助:“请检查您的互联网连接”
- 提供下一步:“重试"或"联系支持”
- 匹配严重性:关键错误比警告需要更多关注
不应该
- 显示技术细节:
TypeError: Cannot read property 'map' of undefined - 模糊:“发生错误”
- 责怪用户:“您输入了无效数据”
- 使用行话:“HTTP 500 内部服务器错误”
错误处理模式
API/网络错误
async function fetchWithRetry(url: string, retries = 3): Promise<Response> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} catch (error) {
if (retries > 0) {
await sleep(1000);
return fetchWithRetry(url, retries - 1);
}
throw error;
}
}
表单验证
function validateForm(data: FormData) {
const errors: Record<string, string> = {};
if (!data.email) {
errors.email = '电子邮件是必需的';
} else if (!isValidEmail(data.email)) {
errors.email = '请输入有效的电子邮件';
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
React错误边界
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, info);
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
标准参考
参见详细模式:
/standards/global/error-handling.md
需要指出的红旗
| 红旗 | 询问的问题 |
|---|---|
| 空捕获块 | “当这失败时会发生什么?” |
catch (e) { return null } |
“调用者如何知道它失败了?” |
| 无加载状态 | “用户在等待时看到什么?” |
| 显示技术错误 | “用户会理解这条消息吗?” |
| 无finally进行清理 | “加载状态在错误时是否卡住了?” |
| console.log而不是错误 | “您将如何在生产日志中找到这个?” |