1. 异常处理的基本认知
在编程实践中,错误和异常就像开车时突然遇到的路障——你永远不知道它们会在哪个路口出现,但必须提前准备好应对方案。try-catch机制就是程序员手中的"应急工具箱",它允许我们在代码执行过程中捕获并处理意外情况,而不是让整个程序崩溃退出。
我见过太多新手开发者写出这样的代码:
javascript复制function readUserFile(filename) {
const content = fs.readFileSync(filename);
return JSON.parse(content);
}
当文件不存在或者内容不是合法JSON时,这样的代码会直接抛出未处理的异常导致进程终止。更糟糕的是,在生产环境中,这类未捕获的异常可能造成数据不一致或资源泄漏。
try-catch的基本结构就像安全气囊系统:
javascript复制try {
// 可能出错的代码(驾驶过程)
} catch (error) {
// 错误处理(气囊弹出)
} finally {
// 必须执行的清理(解开安全带)
}
关键认知:异常处理不是用来掩盖错误的创可贴,而是保证程序健壮性的设计策略。就像飞机上的黑匣子,它既要在故障时保护关键系统,又要完整记录问题现场。
2. try-catch的深层工作机制
2.1 执行栈与错误冒泡
当代码抛出异常时,JavaScript引擎会立即停止当前执行栈,并开始逐层向上查找最近的catch块。这个过程类似于事件冒泡:
- 引擎暂停当前函数执行
- 销毁当前执行上下文
- 检查是否有catch处理器
- 如果没有,继续向调用栈上层查找
- 直到找到处理器或到达全局作用域
javascript复制function A() {
B(); // 调用B时抛出异常
}
function B() {
throw new Error("critical failure");
}
try {
A();
} catch (e) {
console.log("Caught:", e.message); // 捕获到B抛出的错误
}
2.2 Error对象的秘密
标准的Error对象包含这些关键属性:
name: 错误类型(TypeError/ReferenceError等)message: 人类可读的描述stack: 调用栈快照(非标准但广泛支持)
我们可以自定义错误类型:
javascript复制class DatabaseError extends Error {
constructor(query, params) {
super(`DB operation failed`);
this.query = query;
this.params = params;
this.code = 'EDBFAIL';
}
}
try {
throw new DatabaseError('SELECT * FROM users', {id: 123});
} catch (e) {
if (e.code === 'EDBFAIL') {
console.error('Failed query:', e.query);
}
}
3. 高级应用模式
3.1 条件捕获策略
不是所有错误都需要相同级别的处理。我们可以像医院分诊系统那样对异常分类处理:
javascript复制try {
// 业务代码
} catch (e) {
if (e instanceof NetworkError) {
retryAfterDelay();
} else if (e instanceof ValidationError) {
showUserError(e.message);
} else {
logToMonitoringSystem(e);
throw e; // 重新抛出未知错误
}
}
3.2 Promise与async/await中的陷阱
异步代码中的错误处理有其特殊性:
javascript复制// 错误方式:这无法捕获Promise拒绝
try {
fetchData().then(data => {
throw new Error('Oops');
});
} catch (e) {
// 永远不会执行到这里
}
// 正确方式1:在Promise链中使用catch
fetchData()
.then(data => {
throw new Error('Oops');
})
.catch(e => {
console.log('Caught:', e);
});
// 正确方式2:async/await中使用try-catch
async function process() {
try {
const data = await fetchData();
throw new Error('Oops');
} catch (e) {
console.log('Caught:', e);
}
}
3.3 性能关键代码的优化
try-catch块在V8引擎中的性能特点:
- 单纯的try-catch几乎无性能损耗
- 实际抛出异常时开销较大(需要收集调用栈)
- 避免在热代码路径中频繁抛出异常
优化示例:
javascript复制// 反模式:在循环内使用try-catch
for (let i = 0; i < 1e6; i++) {
try {
riskyOperation();
} catch (e) {
// ...
}
}
// 优化方案:将try-catch提到循环外部
try {
for (let i = 0; i < 1e6; i++) {
riskyOperation();
}
} catch (e) {
// ...
}
4. 实战中的黄金法则
4.1 错误处理策略矩阵
根据错误类型采取不同策略:
| 错误类型 | 处理策略 | 示例 |
|---|---|---|
| 编程错误 | 立即失败 | null引用 |
| 资源错误 | 重试或降级 | 数据库连接失败 |
| 业务规则错误 | 用户反馈 | 表单验证失败 |
| 外部服务错误 | 熔断/回退 | API限流 |
| 数据不一致 | 修复/报警 | 数据校验失败 |
4.2 日志记录的最佳实践
有效的错误日志应该包含:
- 时间戳和唯一标识符
- 完整的错误堆栈
- 相关业务上下文
- 严重程度分级
javascript复制try {
// ...
} catch (e) {
logger.error({
message: 'Order processing failed',
error: e.stack,
orderId: currentOrder.id,
userId: currentUser.id,
severity: 'CRITICAL'
});
throw new AppError('ORDER_PROCESS_FAILED');
}
4.3 防御性编程技巧
- 参数验证前置:
javascript复制function processUser(user) {
if (!user?.id) {
throw new ValidationError('Invalid user object');
}
// 主逻辑
}
- 安全的数据访问:
javascript复制function getConfigValue(key) {
try {
return configManager.getValue(key);
} catch (e) {
return defaults[key];
}
}
- 资源清理保障:
javascript复制let resource;
try {
resource = acquireResource();
// 使用资源
} finally {
if (resource) {
resource.release();
}
}
5. 常见反模式与解决方案
5.1 过度捕获问题
反模式:捕获所有错误却不做恰当处理
javascript复制try {
// 业务代码
} catch (e) {
console.log('Something went wrong');
// 继续执行可能不安全的代码
}
修正方案:
javascript复制try {
// 业务代码
} catch (e) {
if (isRecoverable(e)) {
recover();
} else {
throw e; // 重新抛出不可恢复的错误
}
}
5.2 忽略异步错误
反模式:忽略Promise拒绝
javascript复制async function updateData() {
try {
await fetch('/api');
} catch (e) {
// 没有处理或记录错误
}
}
修正方案:
javascript复制async function updateData() {
try {
await fetch('/api');
} catch (e) {
logger.error('API update failed', e);
showUserNotification('Update failed, please retry');
throw e; // 让调用方知晓失败
}
}
5.3 错误信息泄露
反模式:向客户端暴露敏感信息
javascript复制try {
db.query('SELECT * FROM users WHERE id = ?', [id]);
} catch (e) {
res.status(500).send(`Database error: ${e.message}`);
}
修正方案:
javascript复制try {
db.query('SELECT * FROM users WHERE id = ?', [id]);
} catch (e) {
logger.error('DB query failed', { error: e, query: 'SELECT users' });
res.status(500).send('Service unavailable');
}
6. 现代JavaScript中的新特性
6.1 Error Cause链式追踪
ES2022新增的cause属性允许关联错误:
javascript复制async function fetchWithRetry(url) {
try {
return await fetch(url);
} catch (e) {
throw new Error(`Failed to fetch ${url} after retry`, { cause: e });
}
}
try {
await fetchWithRetry('https://api.example.com');
} catch (e) {
console.log(e.message); // 新错误信息
console.log(e.cause); // 原始错误对象
}
6.2 AggregateError
处理多个并行操作的错误集合:
javascript复制async function fetchMultiple(urls) {
try {
const results = await Promise.allSettled(urls.map(fetch));
const errors = results.filter(r => r.status === 'rejected');
if (errors.length) {
throw new AggregateError(errors.map(e => e.reason));
}
return results.map(r => r.value);
} catch (e) {
if (e instanceof AggregateError) {
console.log(`Failed ${e.errors.length} requests`);
}
throw e;
}
}
6.3 模块级别的错误处理
在ES模块中可以使用顶层await处理初始化错误:
javascript复制// config.js
let config;
try {
config = await loadConfig();
} catch (e) {
config = getFallbackConfig();
}
export { config };
// app.js
import { config } from './config.js';
// 使用已处理的配置
7. 浏览器与Node.js的差异
7.1 全局错误处理
浏览器环境:
javascript复制// 未捕获的Promise拒绝
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled rejection:', event.reason);
});
// 常规错误
window.onerror = (message, source, lineno, colno, error) => {
// 发送错误日志
return true; // 阻止默认错误提示
};
Node.js环境:
javascript复制process.on('uncaughtException', err => {
console.error('Crash:', err);
// 必须优雅关闭进程
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
});
7.2 错误类型差异
浏览器特有的错误类型:
- DOMException:DOM操作错误
- MediaError:音视频相关错误
Node.js特有的错误类型:
- SystemError:系统级错误(如文件权限)
- HttpError:HTTP模块错误
7.3 堆栈追踪差异
Node.js的Error.captureStackTrace:
javascript复制function MyError(message) {
Error.captureStackTrace(this, MyError);
this.message = message;
}
try {
throw new MyError('custom error');
} catch (e) {
console.log(e.stack); // 显示自定义的堆栈起点
}
8. TypeScript中的强化实践
8.1 类型安全的错误处理
typescript复制interface BusinessError extends Error {
code: string;
context?: Record<string, unknown>;
}
function isBusinessError(err: unknown): err is BusinessError {
return err instanceof Error && 'code' in err;
}
try {
// ...
} catch (err) {
if (isBusinessError(err)) {
handleBusinessError(err);
} else {
handleUnexpectedError(err);
}
}
8.2 never类型与穷尽检查
typescript复制function handleError(err: NetworkError | DatabaseError) {
switch (err.code) {
case 'NETWORK_FAILURE':
return retry();
case 'DB_CONNECTION':
return useCache();
default:
// 确保处理了所有已知类型
const _exhaustiveCheck: never = err;
return _exhaustiveCheck;
}
}
8.3 声明自定义错误类型
typescript复制class ValidationError extends Error {
constructor(
public field: string,
public rule: string,
message?: string
) {
super(message ?? `Validation failed for ${field}`);
this.name = 'ValidationError';
}
}
// 使用
try {
throw new ValidationError('email', 'required');
} catch (e) {
if (e instanceof ValidationError) {
console.log(e.field, e.rule);
}
}
9. 调试技巧与工具链
9.1 Chrome DevTools技巧
- 开启"Pause on exceptions"选项
- 使用条件断点捕获特定错误
- 查看完整的错误堆栈和作用域链
9.2 VS Code调试配置
json复制{
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug with error break",
"stopOnEntry": false,
"breakOnError": true
}
]
}
9.3 性能分析中的错误追踪
使用Node.js的async_hooks跟踪异步错误:
javascript复制const async_hooks = require('async_hooks');
const fs = require('fs');
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
fs.writeSync(1, `Init ${type} ${asyncId}\n`);
},
destroy(asyncId) {
fs.writeSync(1, `Destroy ${asyncId}\n`);
}
});
hook.enable();
process.on('uncaughtException', err => {
console.error('Uncaught exception with async context');
});
10. 测试策略与Mock错误
10.1 单元测试中的错误断言
javascript复制// Jest示例
test('throws invalid input', () => {
expect(() => {
parseInput(null);
}).toThrow('Invalid input');
});
// 异步错误测试
test('rejects with network error', async () => {
await expect(fetchData('invalid'))
.rejects
.toThrow(NetworkError);
});
10.2 模拟错误场景
javascript复制// 使用jest模拟模块错误
jest.mock('fs', () => ({
readFile: jest.fn(() => {
throw new Error('File read error');
})
}));
test('handles file read error', async () => {
await expect(loadConfig())
.rejects
.toThrow('Config load failed');
});
10.3 错误注入测试
javascript复制function testErrorRecovery() {
const original = Database.connect;
try {
// 注入错误
Database.connect = () => {
throw new DatabaseError('Connection failed');
};
const result = fallbackToCache();
expect(result).toEqual(cachedData);
} finally {
// 恢复原始实现
Database.connect = original;
}
}
11. 领域特定实践
11.1 React组件错误边界
jsx复制class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
11.2 Express中间件错误处理
javascript复制// 错误处理中间件
app.use(async (err, req, res, next) => {
if (err instanceof AuthError) {
res.status(401).json({ error: err.message });
} else {
await sendToMonitoring(err);
res.status(500).end();
}
});
// 路由中使用
app.post('/api', async (req, res, next) => {
try {
const data = await processRequest(req.body);
res.json(data);
} catch (e) {
next(e); // 传递给错误中间件
}
});
11.3 Web Worker错误处理
javascript复制// worker.js
self.addEventListener('error', event => {
event.preventDefault();
self.postMessage({
type: 'ERROR',
error: event.message
});
});
// 主线程
worker.onmessage = ({ data }) => {
if (data.type === 'ERROR') {
handleWorkerError(data.error);
}
};
12. 性能监控与APM集成
12.1 Sentry配置示例
javascript复制import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'your_dsn',
tracesSampleRate: 1.0,
attachStacktrace: true,
beforeSend(event) {
if (event.exception) {
console.log('Captured error:', event.exception.values[0].value);
}
return event;
}
});
// 捕获未处理异常
process.on('uncaughtException', err => {
Sentry.captureException(err);
process.exit(1);
});
12.2 自定义性能指标
javascript复制function trackErrorRate() {
let errorCount = 0;
let totalRequests = 0;
return {
before() {
totalRequests++;
},
captureError() {
errorCount++;
},
getMetrics() {
return {
errorRate: (errorCount / totalRequests) * 100,
totalErrors: errorCount
};
}
};
}
// 使用
const metrics = trackErrorRate();
try {
metrics.before();
riskyOperation();
} catch (e) {
metrics.captureError();
}
12.3 分布式追踪中的错误传播
javascript复制const { trace } = require('@opentelemetry/api');
async function serviceCall() {
const span = trace.getActiveSpan();
try {
return await fetch('http://service');
} catch (e) {
span.recordException(e);
span.setStatus({ code: SpanStatusCode.ERROR });
throw e;
}
}
13. 安全考量与错误处理
13.1 防止敏感信息泄露
javascript复制function sanitizeError(err) {
const safeError = new Error('Internal server error');
safeError.stack = process.env.NODE_ENV === 'development'
? err.stack
: undefined;
return safeError;
}
try {
// ...
} catch (err) {
const clientError = sanitizeError(err);
res.status(500).json({ error: clientError.message });
}
13.2 错误与重试安全
javascript复制async function safeRetry(operation, maxAttempts = 3) {
let attempt = 0;
while (attempt < maxAttempts) {
try {
return await operation();
} catch (e) {
if (!isRetryable(e)) throw e;
attempt++;
await delay(attempt * 1000);
}
}
throw new Error(`Max retries (${maxAttempts}) exceeded`);
}
function isRetryable(error) {
return error.code === 'ETIMEDOUT' ||
error.code === 'ECONNRESET';
}
13.3 审计日志集成
javascript复制function logSecurityError(error) {
const auditLog = {
timestamp: new Date(),
userId: currentUser?.id || 'anonymous',
errorType: error.name,
severity: 'HIGH',
operation: 'login',
ip: request.ip
};
securityLogger.write(auditLog);
}
try {
authenticate(request);
} catch (e) {
if (e instanceof AuthError) {
logSecurityError(e);
}
throw e;
}
14. 架构设计中的错误处理
14.1 分层错误处理策略
mermaid复制graph TD
A[表示层] -->|捕获UI错误| B(显示友好错误)
C[业务层] -->|处理业务异常| D(记录并转换错误)
E[数据层] -->|原始错误| C
F[外部服务] -->|网络错误| E
14.2 断路器模式实现
javascript复制class CircuitBreaker {
constructor(request, options = {}) {
this.state = 'CLOSED';
this.failureCount = 0;
this.request = request;
this.options = {
failureThreshold: 3,
successThreshold: 2,
timeout: 5000,
...options
};
}
async fire() {
if (this.state === 'OPEN') {
throw new Error('Circuit breaker is open');
}
try {
const response = await this.request();
this.success();
return response;
} catch (e) {
this.fail();
throw e;
}
}
success() {
if (this.state === 'HALF') {
this.successCount++;
if (this.successCount > this.options.successThreshold) {
this.close();
}
}
}
fail() {
this.failureCount++;
if (this.failureCount >= this.options.failureThreshold) {
this.open();
}
}
open() {
this.state = 'OPEN';
setTimeout(() => this.half(), this.options.timeout);
}
half() {
this.state = 'HALF';
this.successCount = 0;
}
close() {
this.state = 'CLOSED';
this.failureCount = 0;
}
}
14.3 领域驱动设计中的错误处理
typescript复制class Money {
private constructor(
public readonly amount: number,
public readonly currency: string
) {}
static create(amount: number, currency: string): Result<Money, DomainError> {
if (amount < 0) {
return Result.fail(new DomainError('Amount cannot be negative'));
}
if (!VALID_CURRENCIES.includes(currency)) {
return Result.fail(new DomainError(`Invalid currency: ${currency}`));
}
return Result.ok(new Money(amount, currency));
}
}
// 使用
const moneyResult = Money.create(100, 'USD');
if (moneyResult.isFailure) {
handleError(moneyResult.error);
}
15. 错误处理与用户体验
15.1 用户友好的错误消息
错误消息设计原则:
- 明确发生了什么问题
- 解释为什么会发生
- 提供解决方案或下一步操作
- 保持专业但友好的语气
javascript复制const ERROR_MESSAGES = {
NETWORK_ERROR: {
title: "Connection lost",
detail: "We couldn't reach our servers. Please check your internet connection.",
action: "Retry in 5 seconds",
icon: "wifi-off"
},
PAYMENT_FAILED: {
title: "Payment declined",
detail: "Your transaction couldn't be processed. This may be due to insufficient funds.",
action: "Try another payment method",
icon: "credit-card"
}
};
function showUserError(code) {
const { title, detail, action, icon } = ERROR_MESSAGES[code] || DEFAULT_ERROR;
displayToast({ title, message: `${detail} ${action}`, icon });
}
15.2 渐进式错误披露
javascript复制function handleAPIError(error) {
if (error.status === 401) {
showLoginModal();
} else if (error.status === 429) {
showRateLimitMessage(error.retryAfter);
} else {
showGenericError({
userTitle: "Something went wrong",
userMessage: "We're working on fixing this issue",
technicalDetails: process.env.NODE_ENV === 'development'
? error.stack
: `Error ID: ${error.id}`
});
}
}
15.3 错误恢复UI模式
- 局部错误展示(不打断整体流程)
- 自动重试机制
- 备用内容展示
- 操作回退选项
jsx复制function DataTable() {
const { data, error, retry } = useFetch('/api/data');
if (error) {
return (
<div className="table-error">
<Alert message="Data load failed" />
<Button onClick={retry}>Retry</Button>
<Table data={cachedData} /> {/* 降级数据 */}
</div>
);
}
return <Table data={data} />;
}
16. 错误处理与团队协作
16.1 错误代码规范
团队应制定统一的错误代码体系:
code复制格式: [来源][类别][编号]
示例:
- AUTH-401-001: 无效的API密钥
- DB-503-002: 数据库连接池耗尽
- VALIDATION-400-003: 邮箱格式无效
16.2 错误处理文档模板
markdown复制## [错误代码] 简短描述
**触发条件**:
- 当用户尝试...时
- 当系统检测到...时
**处理建议**:
1. 首先检查...
2. 如果问题持续...
3. 必要时...
**相关日志**:
- 查找字段: `error_code: [代码]`
- 典型堆栈特征: ...
**负责人**: [团队/个人]
16.3 错误复盘流程
- 错误发现与分类
- 影响范围评估
- 根本原因分析
- 短期修复方案
- 长期预防措施
- 文档更新与知识共享
17. 错误处理与DevOps
17.1 告警规则配置
Prometheus告警规则示例:
yaml复制groups:
- name: error-rates
rules:
- alert: HighErrorRate
expr: rate(http_request_errors_total[5m]) / rate(http_requests_total[5m]) > 0.05
for: 10m
labels:
severity: page
annotations:
summary: "High error rate ({{ $value }}) on {{ $labels.service }}"
17.2 错误指标收集
关键指标示例:
- 错误率(错误请求数/总请求数)
- 错误类型分布
- 首次发生与最后发生时间
- 影响用户数
- 平均修复时间(MTTR)
17.3 自动化修复策略
javascript复制// 基于错误的自动修复流程
async function handleDatabaseError(error) {
if (error.code === 'ECONNRESET') {
await pool.drain();
await pool.reconnect();
return { action: 'reconnected', success: true };
}
return { action: 'none', success: false };
}
18. 前沿趋势与未来方向
18.1 机器学习辅助错误诊断
新兴工具开始利用:
- 错误模式识别
- 相似历史案例推荐
- 自动修复建议生成
- 影响预测模型
18.2 全链路错误追踪
分布式系统中的挑战:
- 跨服务错误传播
- 因果关系重建
- 影响范围可视化
- 根因定位加速
18.3 错误处理即代码
基础设施即代码的延伸:
yaml复制# 错误处理策略定义
error_policies:
- match:
code: "ETIMEDOUT"
actions:
- retry:
attempts: 3
backoff: 1s
- fallback:
command: "scripts/use_cached_data.sh"
- alert:
severity: warning
19. 跨语言错误处理比较
19.1 Go语言的错误处理
go复制func ReadFile(name string) ([]byte, error) {
f, err := os.Open(name)
if err != nil {
return nil, fmt.Errorf("open failed: %w", err)
}
defer f.Close()
// ...
}
19.2 Rust的Result类型
rust复制fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
19.3 Python的异常体系
python复制try:
with open('file.txt') as f:
content = f.read()
except FileNotFoundError as e:
print(f"Missing file: {e}")
except IOError as e:
print(f"I/O error: {e}")
else:
process(content)
finally:
cleanup()
20. 个人经验与实用技巧
20.1 错误处理检查清单
在代码审查时我会检查:
- 是否捕获了正确层级的异常?
- 错误信息是否足够调试?
- 是否有敏感信息泄露风险?
- 资源是否被正确释放?
- 是否有适当的重试机制?
- 用户是否得到清晰反馈?
- 监控系统是否能捕获此错误?
20.2 我的错误处理工具箱
常用工具集:
debug模块:条件调试日志pino/winston:结构化日志verror:错误链包装shutdown-cleanup:进程退出处理retry-axios:自动重试请求
20.3 值得警惕的坏味道
- 空的catch块
- 过于宽泛的异常捕获(catch Error)
- 忽略Promise拒绝
- 错误消息硬编码
- 多层嵌套的try-catch
- 混淆业务错误与技术错误
21. 案例研究:电商系统错误处理
21.1 下单流程的防御层
javascript复制async function placeOrder(orderData) {
try {
// 验证层
validateOrderData(orderData);
// 业务逻辑层
const payment = await processPayment(orderData);
const inventory = await reserveInventory(orderData.items);
// 持久化层
const order = await createOrderRecord({
...orderData,
paymentId: payment.id,
inventoryReservationId: inventory.id
});
return order;
} catch (e) {
// 补偿操作
await rollbackPayment(e.paymentId);
await releaseInventory(e.inventoryReservationId);
// 错误转换
throw mapToUserFriendlyError(e);
}
}
21.2 支付处理的状态机
javascript复制class PaymentProcessor {
state = 'INITIAL';
async execute() {
try {
this.state = 'PROCESSING';
await this.validate();
this.state = 'CHARGING';
await this.charge();
this.state = 'CONFIRMING';
await this.confirm();
this.state = 'COMPLETED';
} catch (e) {
this.state = 'FAILED';
await this.handleFailure(e);
throw e;
}
}
async handleFailure(error) {
const handler = this.errorHandlers[this.state];
await handler(error);
}
errorHandlers = {
PROCESSING: async (e) => {
// 验证失败无需回滚
},
CHARGING: async (e) => {
await this.refundPartialCharge();
},
CONFIRMING: async (e) => {
await this.reversePayment();
}
};
}
21.3 库存服务的最终一致性
javascript复制async function reserveInventory(items) {
try {
const result = await inventoryService.reserve(items);
return result;
} catch (e) {
if (e.code === 'INSUFFICIENT_STOCK') {
// 触发补货流程
await triggerRestock(items);
throw new OutOfStockError(items);
}
throw e;
}
}
// 后台任务处理预留超时
async function processExpiredReservations() {
const expired = await findExpiredReservations();
for (const res of expired) {
try {
await releaseReservation(res.id);
} catch (e) {
logger.error('Failed to release reservation', res.id);
}
}
}
22. 错误处理与代码可维护性
22.1 错误处理与单一职责
javascript复制// 反模式:业务逻辑与错误处理混杂
function processUser(user) {
try {
if (!user.name) {
throw new Error('Name required');
}
// 业务逻辑...
} catch (e) {
logger.error(e);
throw e;
}
}
// 优化方案:分离验证逻辑
function validateUser(user) {
if (!user.name) throw new ValidationError('Name required');
// 其他验证...
}
function processUser(user) {
try {
validateUser(user);
// 纯业务逻辑...
} catch (e) {
handleProcessingError(e);
}
}
22.2 错误处理中间件模式
javascript复制// 错误处理装饰器
function withErrorHandling(handler) {
return async (...args) => {
try {
return await handler(...args);
} catch (e) {
if (e instanceof DomainError) {
return { error: e.message };
}
throw e;
}
};
}
// 使用
const safeHandler = withErrorHandling(async (req) => {
// 业务逻辑
});
22.3 测试友好的错误设计
javascript复制// 难以测试的实现
function calculateDiscount(order) {
try {
// 复杂计算...
} catch (e) {
console.error(e);
return 0;
}
}
// 可测试的改进
function calculateDiscount(order, errorHandler = console.error) {
try {
// 复杂计算...
} catch (e) {
errorHandler(e);
return 0;
}
}
// 测试中
test('logs calculation errors', () => {
const mockErrorHandler = jest.fn();
calculateDiscount(null, mockErrorHandler);
expect(mockErrorHandler).toHaveBeenCalled();
});
23. 错误处理与性能优化
23.1 热点路径的错误预防
javascript复制// 数据校验前置减少异常抛出
function processItems(items) {
// 提前过滤无效项
const validItems = items.filter(item =>
item != null && item.id && item.quantity > 0
);
// 主处理逻辑不需要try-catch
return validItems.map(transformItem);
}
23.2 错误收集的批处理
javascript复制class ErrorBatcher {
constructor() {
this.errors = [];
this.flushInterval = setInterval(() => this.flush(), 5000);
}
add(error) {
this.errors.push(error);
if (this.errors.length >= 100) {
this.flush();
}
}
flush() {
if (this.errors.length > 0) {
sendToAnalytics(this.errors);
this.errors = [];
}
}
}
// 使用
const batcher = new ErrorBatcher();
try {
// ...
} catch (e) {
batcher.add(e);
}
23.3 关键路径的错误缓存
javascript复制let lastErrorTime = 0;
const ERROR_THROTTLE = 5000; // 5秒
function logCriticalError(error) {
const now = Date.now();