name: midnight-dapp:error-handling description: 在Midnight DApp中实现错误处理时使用,包括向用户显示错误、处理证明生成失败、管理交易拒绝或处理网络断开连接。
错误处理
在Midnight DApp中优雅地处理错误,提供用户友好的消息、正确的错误分类和恢复策略。
何时使用
- 以有帮助的方式向用户显示错误
- 为React组件实现错误边界
- 处理证明生成失败
- 管理交易拒绝和合约错误
- 处理网络断开连接(索引器、证明服务器)
- 在DApp中创建一致的错误消息
关键概念
错误分类
Midnight DApp会遇到以下几类错误:
| 类别 | 来源 | 示例 |
|---|---|---|
| 证明错误 | ZK证明生成 | 超时、约束违反、见证失败 |
| 交易错误 | 合约执行 | 余额不足、状态冲突、拒绝 |
| 网络错误 | 基础设施 | 索引器宕机、证明服务器不可用、WebSocket断开连接 |
| 钱包错误 | 用户交互 | 未安装、拒绝、错误网络 |
用户友好消息
技术错误应转换为可操作的消息:
// 技术错误
"电路约束在门1042处失败:输入 > 最大值"
// 用户友好消息
"金额超过允许的最大值。请输入较小的金额。"
恢复模式
不同的错误需要不同的恢复策略:
- 可重试:超时、临时网络问题 - 提供重试按钮
- 需要用户操作:错误网络、余额不足 - 引导用户
- 致命错误:合约错误、无效状态 - 显示支持联系方式
参考资料
| 文档 | 描述 |
|---|---|
| proof-errors.md | ZK证明失败和诊断 |
| transaction-errors.md | 合约执行错误 |
| network-errors.md | 连接和超时处理 |
| user-messaging.md | 错误显示的UX指南 |
示例
| 示例 | 描述 |
|---|---|
| error-boundary/ | Midnight的React错误边界 |
| error-toast/ | Toast通知组件 |
| typed-errors/ | 自定义错误类和目录 |
快速开始
1. 创建类型化错误
import { MidnightError, ErrorCode } from './MidnightErrors';
// 在代码中抛出类型化错误
throw new MidnightError(
ErrorCode.PROOF_TIMEOUT,
'证明生成超时',
{ timeoutMs: 60000, operation: 'transfer' }
);
2. 分类未知错误
import { classifyError } from './errorUtils';
try {
await contract.callTx.transfer(recipient, amount, witnesses);
} catch (error) {
const classified = classifyError(error);
// { code, message, userMessage, suggestion, retryable, category }
}
3. 显示给用户
import { useErrorToast } from './useErrorToast';
function TransferButton() {
const { showError } = useErrorToast();
const handleTransfer = async () => {
try {
await performTransfer();
} catch (error) {
showError(error); // 显示用户友好的toast
}
};
}
4. 包装组件
import { MidnightErrorBoundary } from './MidnightErrorBoundary';
function App() {
return (
<MidnightErrorBoundary
onReset={() => window.location.reload()}
fallback={<ErrorPage />}
>
<MyDApp />
</MidnightErrorBoundary>
);
}
常见模式
错误分类工具
interface ClassifiedError {
code: string;
message: string;
userMessage: string;
suggestion: string;
retryable: boolean;
category: 'proof' | 'transaction' | 'network' | 'wallet' | 'unknown';
}
function classifyError(error: unknown): ClassifiedError {
if (error instanceof MidnightError) {
return {
code: error.code,
message: error.message,
userMessage: ERROR_CATALOG[error.code].userMessage,
suggestion: ERROR_CATALOG[error.code].suggestion,
retryable: ERROR_CATALOG[error.code].retryable,
category: ERROR_CATALOG[error.code].category,
};
}
// 通过错误消息模式分类
const message = error instanceof Error ? error.message : String(error);
return classifyByMessage(message);
}
带退避的重试
async function withRetry<T>(
operation: () => Promise<T>,
options: {
maxRetries: number;
baseDelayMs: number;
shouldRetry: (error: unknown) => boolean;
}
): Promise<T> {
let lastError: unknown;
for (let attempt = 1; attempt <= options.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (!options.shouldRetry(error) || attempt === options.maxRetries) {
throw error;
}
const delay = options.baseDelayMs * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
错误上下文提供者
const ErrorContext = createContext<ErrorContextValue | null>(null);
function ErrorProvider({ children }: { children: ReactNode }) {
const [errors, setErrors] = useState<ClassifiedError[]>([]);
const addError = useCallback((error: unknown) => {
const classified = classifyError(error);
setErrors(prev => [...prev, classified]);
// 自动消除非关键错误
if (classified.category !== 'wallet') {
setTimeout(() => dismissError(classified.code), 5000);
}
}, []);
const dismissError = useCallback((code: string) => {
setErrors(prev => prev.filter(e => e.code !== code));
}, []);
return (
<ErrorContext.Provider value={{ errors, addError, dismissError }}>
{children}
</ErrorContext.Provider>
);
}
错误处理最佳实践
- 永远不要静默吞掉错误 - 始终记录或显示
- 提供可操作的消息 - 告诉用户可以做什么
- 记录技术细节 - 保留完整的错误信息用于调试
- 使用错误边界 - 防止UI中的级联故障
- 适当分类 - 不同的错误需要不同的处理
- 测试错误路径 - 验证错误UI正常工作
- 考虑可访问性 - 错误消息应与屏幕阅读器兼容
相关技能
wallet-integration- 钱包特定的错误处理proof-handling- 证明生成错误模式transaction-flows- 交易错误恢复state-management- 状态读取错误处理
相关命令
/dapp-check- 验证错误处理模式/dapp-debug errors- 诊断错误处理问题