مدیریت خطا (Error Handling)
«مدیریت خطا (Error Handling)» یعنی کنترل اشتباه ها. با این کار برنامه پایدار می ماند. همچنین پیام ها واضح می شوند و کاربر سردرگم نمی شود.
مدیریت خطای پایه
با بلوک های try و catch شروع کن. اگر چیزی اشتباه شد، خطا را می گیری و واکنش می دهی.
Try/Catch
function divide(a: number, b: number): number {\n if (b === 0) {\n throw new Error('Division by zero');\n }\n return a / b;\n}\n\ntry {\n const result = divide(10, 0);\n console.log(result);\n} catch (error) {\n console.error('An error occurred:', (error as Error).message);\n}\n
نکته: در TS 4+، متغیر catch از نوع unknown است. بنابراین اول نوع را محدود کن.
ساخت خطاهای سفارشی
برای سناریوهای خاص، از کلاس های خطای سفارشی استفاده کن. این کار خوانایی را بهتر می کند.
کلاس های خطای اختصاصی
class ValidationError extends Error {\n constructor(message: string, public field?: string) {\n super(message);\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nclass DatabaseError extends Error {\n constructor(message: string, public code: number) {\n super(message);\n this.name = 'DatabaseError';\n Object.setPrototypeOf(this, DatabaseError.prototype);\n }\n}\n\nfunction validateUser(user: any): void {\n if (!user.name) {\n throw new ValidationError('Name is required', 'name');\n }\n if (!String(user.email).includes('@')) {\n throw new ValidationError('Invalid email format', 'email');\n }\n}\n
نگهبان نوع برای خطا
«نگهبان نوع (Type Guard)» یعنی تابعی که نوع را تشخیص می دهد. سپس با خیال راحت به ویژگی ها دسترسی می گیری.
Type Guard و استفاده در catch
function isErrorWithMessage(error: unknown): error is { message: string } {\n return typeof error === 'object' \n && error !== null \n && 'message' in error \n && typeof (error as Record<string, unknown>).message === 'string';\n}\n\nfunction isValidationError(error: unknown): error is ValidationError {\n return error instanceof ValidationError;\n}\n\ntry {\n validateUser({});\n} catch (error: unknown) {\n if (isValidationError(error)) {\n console.error(`Validation error in ${error.field}: ${error.message}`);\n } else if (isErrorWithMessage(error)) {\n console.error('An error occurred:', error.message);\n } else {\n console.error('An unknown error occurred');\n }\n}\n
الگوی Type Assertion
function assertIsError(error: unknown): asserts error is Error {\n if (!(error instanceof Error)) {\n throw new Error('Caught value is not an Error instance');\n }\n}\n\ntry {\n // ...\n} catch (error) {\n assertIsError(error);\n console.error(error.message);\n}\n
خطاهای Async/Await
در کد ناهمگام، فراخوانی های await را در try/catch بگذار. سپس خطاها را مدیریت کن.
نمونه async/await و Promise
interface User {\n id: number;\n name: string;\n email: string;\n}\n\nasync function fetchUser(userId: number): Promise<User> {\n try {\n const response = await fetch(`/api/users/${userId}`);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n const data = await response.json();\n return data as User;\n } catch (error) {\n if (error instanceof Error) {\n console.error('Failed to fetch user:', error.message);\n }\n throw error;\n }\n}\n\ninterface Post {\n id: number;\n title: string;\n}\n\nfunction fetchUserPosts(userId: number): Promise<Post[]> {\n return fetch(`/api/users/${userId}/posts`)\n .then((response) => {\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n return response.json();\n })\n .then((data) => data as Post[])\n .catch((error) => {\n console.error('Failed to fetch posts:', error);\n return [];\n });\n}\n
جلوگیری از Rejection بدون مدیریت
// Bad\nfetchData().then((data) => {\n console.log(data);\n});\n\n// Good\nfetchData()\n .then((data) => {\n console.log('Success:', data);\n })\n .catch((error) => {\n console.error('Error:', error);\n });\n\n// Intentionally ignored\nvoid fetchData().catch(console.error);\n
Error Boundary در React
کامپوننت «Error Boundary» خطاهای درخت کامپوننت ها را می گیرد. سپس رابط را امن نگه می دارد.
نمونه کلاس ErrorBoundary
import React, { Component, ErrorInfo, ReactNode } from 'react';\n\ninterface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error?: Error;\n}\n\nclass ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n public state: ErrorBoundaryState = {\n hasError: false\n };\n\n public static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error: error };\n }\n\n public componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n console.error('Uncaught error:', error, errorInfo);\n }\n\n public render(): ReactNode {\n if (this.state.hasError) {\n return this.props.fallback ?? (\n <div className=\"error-boundary\">\n <h2>Something went wrong</h2>\n <p>{this.state.error?.message}</p>\n <button onClick={() => this.setState({ hasError: false })}>\n Try again\n </button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\nfunction App(): ReactNode {\n return (\n <ErrorBoundary fallback={<div>Oops! Something broke.</div>}>\n <MyComponent />\n </ErrorBoundary>\n );\n}\n
بهترین روش ها
همیشه خطا را مدیریت کن
هیچ وقت catch خالی نگذار. حداقل، لاگ کن.
// Bad\ntry {\n // ...\n} catch {\n // empty\n}\n\n// Good\ntry {\n // ...\n} catch (error) {\n console.error('Operation failed:', error);\n}\n
نوع های مشخص تعریف کن
برای شبکه و اعتبارسنجی، کلاس جدا بساز. سپس پیام مناسب بده.
class NetworkError extends Error {\n constructor(public status: number, message: string) {\n super(message);\n this.name = 'NetworkError';\n }\n}\n\nclass ValidationErr extends Error {\n constructor(public field: string, message: string) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n
در سطح درست مدیریت کن
جایی که اطلاعات کافی داری، خطا را بگیر. سپس تجربه کاربر را بهتر کن.
async function getUser(id: string): Promise<User> {\n const response = await fetch(`/api/users/${id}`);\n if (!response.ok) {\n throw new NetworkError(response.status, 'Failed to fetch user');\n }\n const data = await response.json();\n return data as User;\n}\n\nasync function loadUser(): Promise<void> {\n try {\n const user = await getUser('123');\n setUser(user);\n } catch (error) {\n if (error instanceof NetworkError) {\n if (error.status === 404) {\n showError('User not found');\n } else {\n showError('Network error. Please try again later.');\n }\n } else {\n showError('An unexpected error occurred');\n }\n }\n}\n
اشتباهات رایج
نادیده گرفتن Rejection
// Bad\nfetchData();\n\n// Good\nfetchData().catch(console.error);\n
بدون محدودسازی نوع در catch
// Bad\ntry {\n // ...\n} catch (error) {\n // error: unknown\n // console.log(error.message);\n}\n\n// Good\ntry {\n // ...\n} catch (error) {\n if (error instanceof Error) {\n console.log(error.message);\n }\n}\n
قورت دادن خطا
// Bad\nfunction saveData(data: unknown): void {\n try {\n database.save(data);\n } catch {\n // ignore\n }\n}\n\n// Better\nfunction saveDataBetter(data: unknown): void {\n try {\n database.save(data);\n } catch (error) {\n console.error('Failed to save data:', error);\n showError('Failed to save data. Please try again.');\n }\n}\n
ادامه مسیر
برای مهاجرت گام به گام، صفحه مهاجرت تایپ اسکریپت را ببین. برای نکته های بیشتر، به بهترین روش ها سر بزن.
جمع بندی سریع
- در
catchنوع را محدود کن. - برای سناریوها، خطای سفارشی بساز.
- در async، همیشه خطا را بگیر.
- Rejection را رها نکن.
- در سطح درست مدیریت کن.