7.1 错误处理基础
7.1.1 JavaScript 错误类型
TypeScript 继承了 JavaScript 的错误处理机制,了解不同类型的错误对于有效的错误处理至关重要:
// 内置错误类型
class ErrorTypes {
static demonstrateErrors() {
try {
// SyntaxError - 语法错误(通常在编译时捕获)
// eval('const x = ;'); // 这会抛出 SyntaxError
// ReferenceError - 引用错误
// console.log(undefinedVariable); // ReferenceError
// TypeError - 类型错误
const nullValue: any = null;
nullValue.someMethod(); // TypeError: Cannot read property 'someMethod' of null
} catch (error) {
if (error instanceof TypeError) {
console.error('Type Error:', error.message);
} else if (error instanceof ReferenceError) {
console.error('Reference Error:', error.message);
} else {
console.error('Unknown Error:', error);
}
}
try {
// RangeError - 范围错误
const arr = new Array(-1); // RangeError: Invalid array length
} catch (error) {
console.error('Range Error:', error.message);
}
try {
// URIError - URI 错误
decodeURIComponent('%'); // URIError: URI malformed
} catch (error) {
console.error('URI Error:', error.message);
}
}
}
// 自定义错误类型
class CustomError extends Error {
constructor(
message: string,
public code: string,
public statusCode?: number
) {
super(message);
this.name = 'CustomError';
// 确保堆栈跟踪正确指向这个构造函数
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
}
}
class ValidationError extends CustomError {
constructor(
message: string,
public field: string,
public value: any
) {
super(message, 'VALIDATION_ERROR', 400);
this.name = 'ValidationError';
}
}
class NetworkError extends CustomError {
constructor(
message: string,
public url: string,
statusCode?: number
) {
super(message, 'NETWORK_ERROR', statusCode);
this.name = 'NetworkError';
}
}
class BusinessLogicError extends CustomError {
constructor(
message: string,
public operation: string,
public context?: Record<string, any>
) {
super(message, 'BUSINESS_LOGIC_ERROR', 422);
this.name = 'BusinessLogicError';
}
}
7.1.2 错误处理策略
// 错误处理策略枚举
enum ErrorHandlingStrategy {
THROW = 'throw',
LOG = 'log',
IGNORE = 'ignore',
RETRY = 'retry',
FALLBACK = 'fallback'
}
// 错误处理配置
interface ErrorHandlingConfig {
strategy: ErrorHandlingStrategy;
maxRetries?: number;
retryDelay?: number;
fallbackValue?: any;
logger?: (error: Error) => void;
}
// 通用错误处理器
class ErrorHandler {
private static defaultConfig: ErrorHandlingConfig = {
strategy: ErrorHandlingStrategy.THROW,
maxRetries: 3,
retryDelay: 1000,
logger: console.error
};
static async handle<T>(
operation: () => Promise<T>,
config: Partial<ErrorHandlingConfig> = {}
): Promise<T | undefined> {
const finalConfig = { ...this.defaultConfig, ...config };
let lastError: Error;
for (let attempt = 1; attempt <= (finalConfig.maxRetries || 1); attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (finalConfig.logger) {
finalConfig.logger(error as Error);
}
if (attempt < (finalConfig.maxRetries || 1) &&
finalConfig.strategy === ErrorHandlingStrategy.RETRY) {
await this.delay(finalConfig.retryDelay || 1000);
continue;
}
break;
}
}
// 处理最终错误
switch (finalConfig.strategy) {
case ErrorHandlingStrategy.THROW:
throw lastError!;
case ErrorHandlingStrategy.LOG:
if (finalConfig.logger) {
finalConfig.logger(lastError!);
}
return undefined;
case ErrorHandlingStrategy.IGNORE:
return undefined;
case ErrorHandlingStrategy.FALLBACK:
return finalConfig.fallbackValue;
default:
throw lastError!;
}
}
private static delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
async function demonstrateErrorHandling() {
// 重试策略
const result1 = await ErrorHandler.handle(
async () => {
if (Math.random() > 0.7) {
throw new NetworkError('Network timeout', 'https://api.example.com');
}
return 'Success!';
},
{
strategy: ErrorHandlingStrategy.RETRY,
maxRetries: 3,
retryDelay: 1000
}
);
// 回退策略
const result2 = await ErrorHandler.handle(
async () => {
throw new Error('Always fails');
},
{
strategy: ErrorHandlingStrategy.FALLBACK,
fallbackValue: 'Default value'
}
);
console.log('Results:', { result1, result2 });
}
7.2 类型安全的错误处理
7.2.1 Result 模式
// Result 类型定义
type Result<T, E = Error> = Success<T> | Failure<E>;
class Success<T> {
readonly isSuccess = true;
readonly isFailure = false;
constructor(public readonly value: T) {}
map<U>(fn: (value: T) => U): Result<U, never> {
return new Success(fn(this.value));
}
flatMap<U, E>(fn: (value: T) => Result<U, E>): Result<U, E> {
return fn(this.value);
}
mapError<F>(_fn: (error: never) => F): Result<T, F> {
return this as any;
}
unwrap(): T {
return this.value;
}
unwrapOr(_defaultValue: T): T {
return this.value;
}
}
class Failure<E> {
readonly isSuccess = false;
readonly isFailure = true;
constructor(public readonly error: E) {}
map<U>(_fn: (value: never) => U): Result<U, E> {
return this as any;
}
flatMap<U, F>(_fn: (value: never) => Result<U, F>): Result<U, E> {
return this as any;
}
mapError<F>(fn: (error: E) => F): Result<never, F> {
return new Failure(fn(this.error));
}
unwrap(): never {
throw this.error;
}
unwrapOr<T>(defaultValue: T): T {
return defaultValue;
}
}
// Result 工厂函数
class ResultFactory {
static success<T>(value: T): Result<T, never> {
return new Success(value);
}
static failure<E>(error: E): Result<never, E> {
return new Failure(error);
}
static fromThrowable<T, E = Error>(
fn: () => T,
errorMapper?: (error: unknown) => E
): Result<T, E> {
try {
return new Success(fn());
} catch (error) {
const mappedError = errorMapper ? errorMapper(error) : error as E;
return new Failure(mappedError);
}
}
static async fromAsyncThrowable<T, E = Error>(
fn: () => Promise<T>,
errorMapper?: (error: unknown) => E
): Promise<Result<T, E>> {
try {
const value = await fn();
return new Success(value);
} catch (error) {
const mappedError = errorMapper ? errorMapper(error) : error as E;
return new Failure(mappedError);
}
}
}
// 使用 Result 模式
interface User {
id: string;
name: string;
email: string;
}
class UserService {
async getUser(id: string): Promise<Result<User, ValidationError | NetworkError>> {
// 验证输入
if (!id || id.trim() === '') {
return ResultFactory.failure(
new ValidationError('User ID is required', 'id', id)
);
}
// 模拟网络请求
return ResultFactory.fromAsyncThrowable(
async () => {
if (Math.random() > 0.8) {
throw new Error('Network timeout');
}
return {
id,
name: 'Alice',
email: 'alice@example.com'
};
},
(error) => new NetworkError(
error instanceof Error ? error.message : 'Unknown network error',
`/api/users/${id}`
)
);
}
async updateUser(
id: string,
updates: Partial<User>
): Promise<Result<User, ValidationError | NetworkError | BusinessLogicError>> {
// 获取用户
const userResult = await this.getUser(id);
if (userResult.isFailure) {
return userResult;
}
const user = userResult.value;
// 业务逻辑验证
if (updates.email && !this.isValidEmail(updates.email)) {
return ResultFactory.failure(
new ValidationError('Invalid email format', 'email', updates.email)
);
}
// 模拟更新操作
return ResultFactory.fromAsyncThrowable(
async () => {
if (Math.random() > 0.9) {
throw new Error('Database error');
}
return { ...user, ...updates };
},
(error) => new BusinessLogicError(
error instanceof Error ? error.message : 'Update failed',
'updateUser',
{ id, updates }
)
);
}
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
// 使用示例
async function demonstrateResultPattern() {
const userService = new UserService();
// 链式操作
const result = await userService.getUser('123')
.then(userResult =>
userResult.flatMap(user =>
userService.updateUser(user.id, { name: 'Bob' })
)
);
if (result.isSuccess) {
console.log('Updated user:', result.value);
} else {
console.error('Operation failed:', result.error);
// 类型安全的错误处理
if (result.error instanceof ValidationError) {
console.error(`Validation error in field ${result.error.field}:`, result.error.message);
} else if (result.error instanceof NetworkError) {
console.error(`Network error for ${result.error.url}:`, result.error.message);
} else if (result.error instanceof BusinessLogicError) {
console.error(`Business logic error in ${result.error.operation}:`, result.error.message);
}
}
}
7.2.2 Option 模式
// Option 类型定义
type Option<T> = Some<T> | None;
class Some<T> {
readonly isSome = true;
readonly isNone = false;
constructor(public readonly value: T) {}
map<U>(fn: (value: T) => U): Option<U> {
return new Some(fn(this.value));
}
flatMap<U>(fn: (value: T) => Option<U>): Option<U> {
return fn(this.value);
}
filter(predicate: (value: T) => boolean): Option<T> {
return predicate(this.value) ? this : new None();
}
unwrap(): T {
return this.value;
}
unwrapOr(_defaultValue: T): T {
return this.value;
}
unwrapOrElse(_fn: () => T): T {
return this.value;
}
}
class None {
readonly isSome = false;
readonly isNone = true;
map<U>(_fn: (value: never) => U): Option<U> {
return this as any;
}
flatMap<U>(_fn: (value: never) => Option<U>): Option<U> {
return this as any;
}
filter(_predicate: (value: never) => boolean): Option<never> {
return this as any;
}
unwrap(): never {
throw new Error('Called unwrap on None');
}
unwrapOr<T>(defaultValue: T): T {
return defaultValue;
}
unwrapOrElse<T>(fn: () => T): T {
return fn();
}
}
// Option 工厂函数
class OptionFactory {
static some<T>(value: T): Option<T> {
return new Some(value);
}
static none<T = never>(): Option<T> {
return new None();
}
static fromNullable<T>(value: T | null | undefined): Option<T> {
return value != null ? new Some(value) : new None();
}
static fromPredicate<T>(value: T, predicate: (value: T) => boolean): Option<T> {
return predicate(value) ? new Some(value) : new None();
}
}
// 使用 Option 模式
class ConfigService {
private config: Record<string, any> = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
get<T>(key: string): Option<T> {
return OptionFactory.fromNullable(this.config[key]);
}
getApiUrl(): Option<string> {
return this.get<string>('apiUrl')
.filter(url => url.startsWith('https://'));
}
getTimeout(): number {
return this.get<number>('timeout')
.filter(timeout => timeout > 0)
.unwrapOr(5000);
}
getRetries(): number {
return this.get<number>('retries')
.filter(retries => retries >= 0 && retries <= 10)
.unwrapOr(3);
}
}
// 链式操作示例
function demonstrateOptionChaining() {
const configService = new ConfigService();
// 安全的链式操作
const result = configService.getApiUrl()
.map(url => `${url}/users`)
.map(url => new URL(url))
.filter(url => url.protocol === 'https:')
.map(url => url.toString());
if (result.isSome) {
console.log('API endpoint:', result.value);
} else {
console.log('Invalid or missing API URL');
}
// 使用 flatMap 进行复杂操作
const userEndpoint = configService.getApiUrl()
.flatMap(baseUrl => {
try {
const url = new URL('/users', baseUrl);
return OptionFactory.some(url.toString());
} catch {
return OptionFactory.none();
}
});
console.log('User endpoint:', userEndpoint.unwrapOr('Not available'));
}
7.3 调试技巧
7.3.1 调试工具和技术
// 调试装饰器
function debug(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.group(`🐛 Debug: ${target.constructor.name}.${propertyKey}`);
console.log('Arguments:', args);
console.time('Execution time');
try {
const result = originalMethod.apply(this, args);
if (result instanceof Promise) {
return result
.then(value => {
console.log('Async result:', value);
console.timeEnd('Execution time');
console.groupEnd();
return value;
})
.catch(error => {
console.error('Async error:', error);
console.timeEnd('Execution time');
console.groupEnd();
throw error;
});
} else {
console.log('Result:', result);
console.timeEnd('Execution time');
console.groupEnd();
return result;
}
} catch (error) {
console.error('Error:', error);
console.timeEnd('Execution time');
console.groupEnd();
throw error;
}
};
return descriptor;
}
// 性能监控装饰器
function performance(threshold: number = 100) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
if (result instanceof Promise) {
return result.finally(() => {
const duration = performance.now() - start;
if (duration > threshold) {
console.warn(
`⚠️ Slow operation: ${target.constructor.name}.${propertyKey} took ${duration.toFixed(2)}ms`
);
}
});
} else {
const duration = performance.now() - start;
if (duration > threshold) {
console.warn(
`⚠️ Slow operation: ${target.constructor.name}.${propertyKey} took ${duration.toFixed(2)}ms`
);
}
return result;
}
};
return descriptor;
};
}
// 调试工具类
class DebugUtils {
private static isDebugMode = process.env.NODE_ENV === 'development';
static log(message: string, ...args: any[]): void {
if (this.isDebugMode) {
console.log(`[DEBUG] ${message}`, ...args);
}
}
static trace(message: string): void {
if (this.isDebugMode) {
console.trace(`[TRACE] ${message}`);
}
}
static assert(condition: boolean, message: string): void {
if (this.isDebugMode && !condition) {
console.assert(condition, message);
}
}
static time(label: string): void {
if (this.isDebugMode) {
console.time(label);
}
}
static timeEnd(label: string): void {
if (this.isDebugMode) {
console.timeEnd(label);
}
}
static profile(label: string): void {
if (this.isDebugMode && console.profile) {
console.profile(label);
}
}
static profileEnd(label: string): void {
if (this.isDebugMode && console.profileEnd) {
console.profileEnd(label);
}
}
static memory(): void {
if (this.isDebugMode && (performance as any).memory) {
const memory = (performance as any).memory;
console.table({
'Used JS Heap Size': `${(memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
'Total JS Heap Size': `${(memory.totalJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
'JS Heap Size Limit': `${(memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2)} MB`
});
}
}
}
// 使用示例
class DataProcessor {
@debug
@performance(50)
async processData(data: any[]): Promise<any[]> {
DebugUtils.log('Starting data processing', { count: data.length });
DebugUtils.time('Data processing');
const result = [];
for (let i = 0; i < data.length; i++) {
DebugUtils.assert(data[i] != null, `Data item at index ${i} is null`);
// 模拟处理
await new Promise(resolve => setTimeout(resolve, 10));
result.push({ ...data[i], processed: true });
if (i % 100 === 0) {
DebugUtils.log(`Processed ${i + 1}/${data.length} items`);
DebugUtils.memory();
}
}
DebugUtils.timeEnd('Data processing');
return result;
}
}
7.3.2 错误边界和全局错误处理
// 全局错误处理器
class GlobalErrorHandler {
private static instance: GlobalErrorHandler;
private errorListeners: Array<(error: Error, context?: any) => void> = [];
private unhandledRejectionListeners: Array<(reason: any, promise: Promise<any>) => void> = [];
private constructor() {
this.setupGlobalHandlers();
}
static getInstance(): GlobalErrorHandler {
if (!this.instance) {
this.instance = new GlobalErrorHandler();
}
return this.instance;
}
private setupGlobalHandlers(): void {
// 处理未捕获的错误
window.addEventListener('error', (event) => {
this.handleError(new Error(event.message), {
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
});
});
// 处理未捕获的 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
this.handleUnhandledRejection(event.reason, event.promise);
event.preventDefault(); // 阻止默认的控制台错误输出
});
}
addErrorListener(listener: (error: Error, context?: any) => void): void {
this.errorListeners.push(listener);
}
addUnhandledRejectionListener(
listener: (reason: any, promise: Promise<any>) => void
): void {
this.unhandledRejectionListeners.push(listener);
}
private handleError(error: Error, context?: any): void {
console.error('Global error caught:', error, context);
this.errorListeners.forEach(listener => {
try {
listener(error, context);
} catch (listenerError) {
console.error('Error in error listener:', listenerError);
}
});
}
private handleUnhandledRejection(reason: any, promise: Promise<any>): void {
console.error('Unhandled promise rejection:', reason, promise);
this.unhandledRejectionListeners.forEach(listener => {
try {
listener(reason, promise);
} catch (listenerError) {
console.error('Error in unhandled rejection listener:', listenerError);
}
});
}
// 手动报告错误
reportError(error: Error, context?: any): void {
this.handleError(error, context);
}
}
// 错误报告服务
class ErrorReportingService {
private static readonly MAX_REPORTS_PER_MINUTE = 10;
private reportCount = 0;
private lastResetTime = Date.now();
constructor(private apiEndpoint: string) {
this.setupGlobalErrorHandling();
}
private setupGlobalErrorHandling(): void {
const globalHandler = GlobalErrorHandler.getInstance();
globalHandler.addErrorListener((error, context) => {
this.reportError({
type: 'javascript_error',
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
});
});
globalHandler.addUnhandledRejectionListener((reason, promise) => {
this.reportError({
type: 'unhandled_rejection',
message: reason instanceof Error ? reason.message : String(reason),
stack: reason instanceof Error ? reason.stack : undefined,
context: { promise: promise.toString() },
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
});
});
}
private async reportError(errorData: any): Promise<void> {
// 限流检查
if (!this.shouldReport()) {
return;
}
try {
await fetch(this.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errorData)
});
console.log('Error reported successfully');
} catch (reportingError) {
console.error('Failed to report error:', reportingError);
}
}
private shouldReport(): boolean {
const now = Date.now();
// 重置计数器(每分钟)
if (now - this.lastResetTime > 60000) {
this.reportCount = 0;
this.lastResetTime = now;
}
// 检查是否超过限制
if (this.reportCount >= ErrorReportingService.MAX_REPORTS_PER_MINUTE) {
return false;
}
this.reportCount++;
return true;
}
}
// 错误边界组件(React 风格)
class ErrorBoundary {
private errorHandler: (error: Error, errorInfo: any) => void;
constructor(errorHandler: (error: Error, errorInfo: any) => void) {
this.errorHandler = errorHandler;
}
wrap<T extends (...args: any[]) => any>(fn: T): T {
return ((...args: any[]) => {
try {
const result = fn(...args);
if (result instanceof Promise) {
return result.catch(error => {
this.errorHandler(error, {
function: fn.name,
arguments: args
});
throw error;
});
}
return result;
} catch (error) {
this.errorHandler(error as Error, {
function: fn.name,
arguments: args
});
throw error;
}
}) as T;
}
}
// 使用示例
const errorReporting = new ErrorReportingService('/api/errors');
const errorBoundary = new ErrorBoundary((error, errorInfo) => {
console.error('Error caught by boundary:', error, errorInfo);
// 可以在这里添加用户通知、错误恢复等逻辑
showUserNotification('An error occurred. Please try again.');
});
// 包装可能出错的函数
const safeAsyncFunction = errorBoundary.wrap(async (data: any) => {
// 可能抛出错误的异步操作
if (!data) {
throw new ValidationError('Data is required', 'data', data);
}
return await processData(data);
});
function showUserNotification(message: string): void {
console.log('User notification:', message);
}
async function processData(data: any): Promise<any> {
// 模拟数据处理
return { processed: true, data };
}
7.3.3 源码映射和调试配置
// tsconfig.json 调试配置示例
/*
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"sourceMap": true, // 生成源码映射
"inlineSourceMap": false, // 不内联源码映射
"inlineSources": false, // 不内联源码
"declaration": true, // 生成声明文件
"declarationMap": true, // 生成声明文件的源码映射
"removeComments": false, // 保留注释用于调试
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
*/
// 调试辅助工具
class DebugBreakpoints {
private static breakpoints = new Map<string, boolean>();
static setBreakpoint(id: string, condition: () => boolean = () => true): void {
this.breakpoints.set(id, true);
if (condition()) {
console.log(`🔴 Breakpoint hit: ${id}`);
debugger; // 触发调试器断点
}
}
static removeBreakpoint(id: string): void {
this.breakpoints.delete(id);
}
static isBreakpointSet(id: string): boolean {
return this.breakpoints.has(id);
}
static listBreakpoints(): string[] {
return Array.from(this.breakpoints.keys());
}
}
// 条件调试
class ConditionalDebug {
static debugIf(condition: boolean, message: string, ...args: any[]): void {
if (condition) {
console.log(`🐛 [CONDITIONAL DEBUG] ${message}`, ...args);
debugger;
}
}
static debugWhen<T>(
value: T,
predicate: (value: T) => boolean,
message: string
): T {
if (predicate(value)) {
console.log(`🐛 [DEBUG WHEN] ${message}`, value);
debugger;
}
return value;
}
static debugOnce(id: string, message: string, ...args: any[]): void {
if (!this.debuggedOnce.has(id)) {
this.debuggedOnce.add(id);
console.log(`🐛 [DEBUG ONCE] ${message}`, ...args);
debugger;
}
}
private static debuggedOnce = new Set<string>();
}
// 使用示例
class DebuggableService {
async fetchData(id: string): Promise<any> {
DebugBreakpoints.setBreakpoint('fetchData:start');
ConditionalDebug.debugIf(
!id,
'fetchData called with empty ID'
);
try {
const response = await fetch(`/api/data/${id}`);
ConditionalDebug.debugWhen(
response.status,
status => status >= 400,
'HTTP error response received'
);
const data = await response.json();
ConditionalDebug.debugOnce(
'first-successful-fetch',
'First successful data fetch'
);
return data;
} catch (error) {
DebugBreakpoints.setBreakpoint('fetchData:error');
throw error;
}
}
}
7.4 测试中的错误处理
7.4.1 错误测试策略
// 测试工具函数
class TestUtils {
static async expectError<T extends Error>(
fn: () => Promise<any> | any,
errorType?: new (...args: any[]) => T
): Promise<T> {
try {
const result = await fn();
throw new Error(`Expected error but got result: ${result}`);
} catch (error) {
if (errorType && !(error instanceof errorType)) {
throw new Error(
`Expected error of type ${errorType.name} but got ${error.constructor.name}`
);
}
return error as T;
}
}
static async expectNoError(fn: () => Promise<any> | any): Promise<any> {
try {
return await fn();
} catch (error) {
throw new Error(`Expected no error but got: ${error}`);
}
}
static mockError(message: string, type: string = 'Error'): Error {
const error = new Error(message);
error.name = type;
return error;
}
}
// 错误模拟器
class ErrorSimulator {
private static errorProbability = 0;
private static errorTypes: Array<() => Error> = [];
static setErrorProbability(probability: number): void {
this.errorProbability = Math.max(0, Math.min(1, probability));
}
static addErrorType(errorFactory: () => Error): void {
this.errorTypes.push(errorFactory);
}
static maybeThrow(): void {
if (Math.random() < this.errorProbability) {
if (this.errorTypes.length > 0) {
const randomErrorFactory = this.errorTypes[
Math.floor(Math.random() * this.errorTypes.length)
];
throw randomErrorFactory();
} else {
throw new Error('Simulated error');
}
}
}
static reset(): void {
this.errorProbability = 0;
this.errorTypes = [];
}
}
// 测试示例
describe('Error Handling Tests', () => {
beforeEach(() => {
ErrorSimulator.reset();
});
describe('UserService', () => {
it('should handle validation errors correctly', async () => {
const userService = new UserService();
const error = await TestUtils.expectError(
() => userService.getUser(''),
ValidationError
);
expect(error.field).toBe('id');
expect(error.code).toBe('VALIDATION_ERROR');
});
it('should handle network errors correctly', async () => {
// 模拟网络错误
ErrorSimulator.setErrorProbability(1);
ErrorSimulator.addErrorType(() => new NetworkError(
'Connection timeout',
'/api/users/123',
408
));
const userService = new UserService();
const error = await TestUtils.expectError(
() => userService.getUser('123'),
NetworkError
);
expect(error.statusCode).toBe(408);
expect(error.url).toBe('/api/users/123');
});
it('should succeed when no errors occur', async () => {
const userService = new UserService();
const result = await TestUtils.expectNoError(
() => userService.getUser('123')
);
expect(result.isSuccess).toBe(true);
if (result.isSuccess) {
expect(result.value.id).toBe('123');
}
});
});
describe('Result Pattern', () => {
it('should chain operations correctly with success', async () => {
const userService = new UserService();
const result = await userService.getUser('123')
.then(userResult =>
userResult.flatMap(user =>
userService.updateUser(user.id, { name: 'Updated Name' })
)
);
expect(result.isSuccess).toBe(true);
if (result.isSuccess) {
expect(result.value.name).toBe('Updated Name');
}
});
it('should handle errors in chain correctly', async () => {
const userService = new UserService();
const result = await userService.getUser('')
.then(userResult =>
userResult.flatMap(user =>
userService.updateUser(user.id, { name: 'Updated Name' })
)
);
expect(result.isFailure).toBe(true);
if (result.isFailure) {
expect(result.error).toBeInstanceOf(ValidationError);
}
});
});
});
// 模拟测试框架函数
function describe(name: string, fn: () => void): void {
console.log(`\n📝 ${name}`);
fn();
}
function it(name: string, fn: () => Promise<void> | void): void {
console.log(` ✓ ${name}`);
Promise.resolve(fn()).catch(error => {
console.error(` ✗ ${name}: ${error.message}`);
});
}
function beforeEach(fn: () => void): void {
fn();
}
const expect = {
toBe: (expected: any) => ({
toBe: (actual: any) => {
if (actual !== expected) {
throw new Error(`Expected ${actual} to be ${expected}`);
}
}
}),
toBeInstanceOf: (constructor: any) => ({
toBeInstanceOf: (actual: any) => {
if (!(actual instanceof constructor)) {
throw new Error(`Expected ${actual} to be instance of ${constructor.name}`);
}
}
})
};
7.5 生产环境错误监控
7.5.1 错误监控和告警
// 错误监控配置
interface MonitoringConfig {
apiKey: string;
environment: string;
version: string;
userId?: string;
sessionId?: string;
enableConsoleCapture: boolean;
enableNetworkCapture: boolean;
enablePerformanceCapture: boolean;
sampleRate: number;
}
// 错误监控服务
class ErrorMonitoringService {
private config: MonitoringConfig;
private errorQueue: any[] = [];
private isOnline = navigator.onLine;
constructor(config: MonitoringConfig) {
this.config = config;
this.setupEventListeners();
this.startPeriodicFlush();
}
private setupEventListeners(): void {
// 网络状态监听
window.addEventListener('online', () => {
this.isOnline = true;
this.flushErrors();
});
window.addEventListener('offline', () => {
this.isOnline = false;
});
// 页面卸载时发送剩余错误
window.addEventListener('beforeunload', () => {
this.flushErrors(true);
});
// 控制台捕获
if (this.config.enableConsoleCapture) {
this.captureConsole();
}
// 网络请求捕获
if (this.config.enableNetworkCapture) {
this.captureNetworkRequests();
}
// 性能监控
if (this.config.enablePerformanceCapture) {
this.capturePerformanceMetrics();
}
}
captureError(error: Error, context?: any): void {
// 采样率检查
if (Math.random() > this.config.sampleRate) {
return;
}
const errorData = {
timestamp: new Date().toISOString(),
message: error.message,
stack: error.stack,
name: error.name,
context,
environment: this.config.environment,
version: this.config.version,
userId: this.config.userId,
sessionId: this.config.sessionId,
url: window.location.href,
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
performance: this.getPerformanceSnapshot()
};
this.errorQueue.push(errorData);
// 立即发送严重错误
if (this.isCriticalError(error)) {
this.flushErrors();
}
}
private isCriticalError(error: Error): boolean {
const criticalPatterns = [
/chunk.*failed/i,
/network.*error/i,
/script.*error/i,
/out of memory/i
];
return criticalPatterns.some(pattern => pattern.test(error.message));
}
private async flushErrors(useBeacon = false): Promise<void> {
if (this.errorQueue.length === 0 || !this.isOnline) {
return;
}
const errors = [...this.errorQueue];
this.errorQueue = [];
try {
if (useBeacon && navigator.sendBeacon) {
// 使用 sendBeacon 在页面卸载时发送数据
navigator.sendBeacon(
'/api/errors',
JSON.stringify({ errors, apiKey: this.config.apiKey })
);
} else {
await fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`
},
body: JSON.stringify({ errors })
});
}
} catch (error) {
// 发送失败,重新加入队列
this.errorQueue.unshift(...errors);
console.error('Failed to send error reports:', error);
}
}
private startPeriodicFlush(): void {
setInterval(() => {
this.flushErrors();
}, 30000); // 每30秒发送一次
}
private captureConsole(): void {
const originalConsoleError = console.error;
console.error = (...args: any[]) => {
originalConsoleError.apply(console, args);
const error = new Error(args.join(' '));
error.name = 'ConsoleError';
this.captureError(error, { type: 'console', arguments: args });
};
}
private captureNetworkRequests(): void {
const originalFetch = window.fetch;
window.fetch = async (...args: Parameters<typeof fetch>) => {
const startTime = performance.now();
try {
const response = await originalFetch(...args);
if (!response.ok) {
const error = new NetworkError(
`HTTP ${response.status}: ${response.statusText}`,
args[0].toString(),
response.status
);
this.captureError(error, {
type: 'network',
url: args[0],
method: args[1]?.method || 'GET',
duration: performance.now() - startTime
});
}
return response;
} catch (error) {
const networkError = new NetworkError(
error instanceof Error ? error.message : 'Network request failed',
args[0].toString()
);
this.captureError(networkError, {
type: 'network',
url: args[0],
method: args[1]?.method || 'GET',
duration: performance.now() - startTime
});
throw error;
}
};
}
private capturePerformanceMetrics(): void {
// 监控长任务
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) { // 长任务阈值
const error = new Error(`Long task detected: ${entry.duration}ms`);
error.name = 'PerformanceWarning';
this.captureError(error, {
type: 'performance',
duration: entry.duration,
startTime: entry.startTime,
entryType: entry.entryType
});
}
}
});
observer.observe({ entryTypes: ['longtask'] });
}
}
private getPerformanceSnapshot(): any {
if (!('performance' in window)) {
return null;
}
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const memory = (performance as any).memory;
return {
navigation: navigation ? {
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
firstPaint: this.getFirstPaint(),
firstContentfulPaint: this.getFirstContentfulPaint()
} : null,
memory: memory ? {
usedJSHeapSize: memory.usedJSHeapSize,
totalJSHeapSize: memory.totalJSHeapSize,
jsHeapSizeLimit: memory.jsHeapSizeLimit
} : null
};
}
private getFirstPaint(): number | null {
const paintEntries = performance.getEntriesByType('paint');
const firstPaint = paintEntries.find(entry => entry.name === 'first-paint');
return firstPaint ? firstPaint.startTime : null;
}
private getFirstContentfulPaint(): number | null {
const paintEntries = performance.getEntriesByType('paint');
const firstContentfulPaint = paintEntries.find(entry => entry.name === 'first-contentful-paint');
return firstContentfulPaint ? firstContentfulPaint.startTime : null;
}
}
// 使用示例
const monitoring = new ErrorMonitoringService({
apiKey: 'your-api-key',
environment: 'production',
version: '1.0.0',
userId: 'user-123',
sessionId: 'session-456',
enableConsoleCapture: true,
enableNetworkCapture: true,
enablePerformanceCapture: true,
sampleRate: 0.1 // 10% 采样率
});
// 集成到全局错误处理
const globalHandler = GlobalErrorHandler.getInstance();
globalHandler.addErrorListener((error, context) => {
monitoring.captureError(error, context);
});
7.6 本章练习
练习 1:实现重试机制
// 实现一个通用的重试机制,支持:
// 1. 指数退避
// 2. 最大重试次数
// 3. 条件重试(只对特定错误重试)
// 4. 重试回调
interface RetryOptions {
maxAttempts: number;
baseDelay: number;
maxDelay: number;
backoffFactor: number;
shouldRetry?: (error: Error, attempt: number) => boolean;
onRetry?: (error: Error, attempt: number) => void;
}
class RetryManager {
// 你的实现
}
// 使用示例
const retryManager = new RetryManager();
const result = await retryManager.execute(
async () => {
// 可能失败的操作
if (Math.random() > 0.7) {
throw new NetworkError('Connection failed', '/api/data');
}
return 'Success!';
},
{
maxAttempts: 5,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2,
shouldRetry: (error, attempt) => {
return error instanceof NetworkError && attempt < 3;
},
onRetry: (error, attempt) => {
console.log(`Retry attempt ${attempt}: ${error.message}`);
}
}
);
练习 2:实现错误恢复策略
// 实现一个错误恢复系统,支持:
// 1. 多种恢复策略
// 2. 策略链
// 3. 恢复状态跟踪
// 4. 恢复历史记录
enum RecoveryStrategy {
RETRY = 'retry',
FALLBACK = 'fallback',
CIRCUIT_BREAKER = 'circuit_breaker',
CACHE = 'cache',
IGNORE = 'ignore'
}
interface RecoveryContext {
error: Error;
attempt: number;
previousResults: any[];
metadata: Record<string, any>;
}
class ErrorRecoveryManager {
// 你的实现
}
// 使用示例
const recoveryManager = new ErrorRecoveryManager();
recoveryManager.addStrategy(RecoveryStrategy.RETRY, {
maxAttempts: 3,
delay: 1000
});
recoveryManager.addStrategy(RecoveryStrategy.FALLBACK, {
fallbackValue: 'Default data'
});
const result = await recoveryManager.execute(async () => {
// 可能失败的操作
throw new Error('Service unavailable');
});
练习 3:实现错误分析器
// 实现一个错误分析器,支持:
// 1. 错误分类
// 2. 错误趋势分析
// 3. 错误影响评估
// 4. 自动告警
interface ErrorPattern {
name: string;
matcher: (error: Error) => boolean;
severity: 'low' | 'medium' | 'high' | 'critical';
category: string;
}
interface ErrorAnalysis {
totalErrors: number;
errorsByCategory: Record<string, number>;
errorsBySeverity: Record<string, number>;
trends: {
hourly: number[];
daily: number[];
};
topErrors: Array<{
message: string;
count: number;
lastOccurrence: Date;
}>;
}
class ErrorAnalyzer {
// 你的实现
}
// 使用示例
const analyzer = new ErrorAnalyzer();
analyzer.addPattern({
name: 'Network Timeout',
matcher: (error) => error.message.includes('timeout'),
severity: 'medium',
category: 'network'
});
analyzer.addPattern({
name: 'Memory Leak',
matcher: (error) => error.message.includes('out of memory'),
severity: 'critical',
category: 'performance'
});
// 分析错误
const analysis = analyzer.analyze(errorHistory);
console.log('Error analysis:', analysis);
7.7 本章总结
本章深入探讨了 TypeScript 中的错误处理和调试技巧,包括:
- 错误处理基础:了解了 JavaScript 错误类型和自定义错误类
- 类型安全的错误处理:学习了 Result 模式和 Option 模式
- 调试技巧:掌握了调试工具、装饰器和条件调试
- 错误边界和全局错误处理:实现了完整的错误处理系统
- 测试中的错误处理:学习了错误测试策略和模拟技术
- 生产环境错误监控:构建了错误监控和告警系统
有效的错误处理和调试是构建健壮应用程序的关键。通过类型安全的错误处理模式、完善的调试工具和生产环境监控,我们可以显著提高应用程序的可靠性和可维护性。
下一章我们将学习工程化配置,了解如何在实际项目中配置和优化 TypeScript 开发环境。