لغة TypeScript

معالجة الأخطاء في تايب سكريبت

35 دقيقة الدرس 21 من 40

معالجة الأخطاء في تايب سكريبت

معالجة الأخطاء هي جانب حاسم في بناء تطبيقات قوية. توفر تايب سكريبت أدوات قوية لمعالجة الأخطاء بطريقة آمنة من حيث الأنواع، متجاوزة آليات try-catch التقليدية في جافا سكريبت. في هذا الدرس، سنستكشف الأخطاء المكتوبة، نمط نوع Result، فئات الأخطاء المخصصة، وحدود الأخطاء لإنشاء تطبيقات مرنة.

فهم الأخطاء المكتوبة

في جافا سكريبت، يلتقط شرط catch قيماً من نوع any. قدمت تايب سكريبت 4.4+ نوع unknown لمتغيرات شرط catch، مما يجعل معالجة الأخطاء أكثر أماناً:

// النهج التقليدي (غير آمن) try { riskyOperation(); } catch (error) { // error من نوع 'any' افتراضياً console.log(error.message); } // تايب سكريبت الحديثة (آمنة) try { riskyOperation(); } catch (error: unknown) { if (error instanceof Error) { console.log(error.message); } else { console.log('خطأ غير معروف:', error); } } // مع دالة حارس النوع المساعدة function isError(error: unknown): error is Error { return error instanceof Error; } try { riskyOperation(); } catch (error) { if (isError(error)) { console.log(error.message); console.log(error.stack); } }
ملاحظة: استخدم دائماً unknown بدلاً من any لمتغيرات شرط catch. هذا يجبرك على فحص نوع الخطأ قبل استخدامه، مما يمنع أخطاء وقت التشغيل.

فئات الأخطاء المخصصة

إنشاء فئات أخطاء مخصصة يسمح لك بتحديد أنواع أخطاء محددة مع سياق إضافي ومعالجة آمنة من حيث النوع:

// خطأ التطبيق الأساسي class AppError extends Error { constructor( message: string, public readonly code: string, public readonly statusCode: number = 500 ) { super(message); this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } } // أنواع أخطاء محددة class ValidationError extends AppError { constructor( message: string, public readonly fields: Record<string, string[]> ) { super(message, 'VALIDATION_ERROR', 400); } } class NotFoundError extends AppError { constructor( resource: string, public readonly id: string | number ) { super(\`${resource} not found\`, 'NOT_FOUND', 404); } } class UnauthorizedError extends AppError { constructor(message: string = 'وصول غير مصرح به') { super(message, 'UNAUTHORIZED', 401); } } // الاستخدام مع حراس الأنواع function handleError(error: unknown): void { if (error instanceof ValidationError) { console.log('فشل التحقق:', error.fields); } else if (error instanceof NotFoundError) { console.log(\`المورد ${error.id} غير موجود\`); } else if (error instanceof UnauthorizedError) { console.log('تم رفض الوصول'); } else if (error instanceof AppError) { console.log(\`خطأ ${error.code}:\`, error.message); } else { console.log('خطأ غير معروف'); } } // مثال على الاستخدام function validateUser(data: unknown): void { throw new ValidationError('بيانات مستخدم غير صالحة', { email: ['البريد الإلكتروني مطلوب', 'تنسيق البريد الإلكتروني غير صالح'], password: ['يجب أن تكون كلمة المرور 8 أحرف على الأقل'] }); } function getUser(id: number): void { throw new NotFoundError('User', id); }
نصيحة: استدعِ دائماً Error.captureStackTrace في بناءات الأخطاء المخصصة للحفاظ على تتبع دقيق للمكدس للتصحيح.

نمط نوع Result

نمط نوع Result هو بديل لرمي الأخطاء، يوفر معالجة صريحة للنجاح/الفشل في وقت الترجمة:

// تعريف نوع Result type Result<T, E = Error> = | { success: true; value: T } | { success: false; error: E }; // دوال مساعدة function ok<T>(value: T): Result<T, never> { return { success: true, value }; } function err<E>(error: E): Result<never, E> { return { success: false, error }; } // مثال على الاستخدام function parseJSON<T>(json: string): Result<T, Error> { try { const value = JSON.parse(json) as T; return ok(value); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } // مطابقة الأنماط مع Result function processResult<T>(result: Result<T>): void { if (result.success) { console.log('نجاح:', result.value); } else { console.log('خطأ:', result.error.message); } } // مثال من العالم الحقيقي interface User { id: number; name: string; email: string; } function fetchUser(id: number): Result<User, NotFoundError> { const user = database.find(id); if (user) { return ok(user); } return err(new NotFoundError('User', id)); } // ربط النتائج function map<T, U, E>( result: Result<T, E>, fn: (value: T) => U ): Result<U, E> { if (result.success) { return ok(fn(result.value)); } return result; } function flatMap<T, U, E>( result: Result<T, E>, fn: (value: T) => Result<U, E> ): Result<U, E> { if (result.success) { return fn(result.value); } return result; } // الاستخدام const userResult = fetchUser(123); const emailResult = map(userResult, user => user.email); const upperEmailResult = map(emailResult, email => email.toUpperCase());
ملاحظة: نمط Result يجعل معالجة الأخطاء صريحة في توقيعات الدوال، مما يجبر المستدعين على معالجة حالات النجاح والفشل.

نوع Option للقيم القابلة للإلغاء

نوع Option مشابه لـ Result لكنه يتعامل تحديداً مع وجود أو غياب قيمة:

// تعريف نوع Option type Option<T> = Some<T> | None; interface Some<T> { readonly kind: 'some'; readonly value: T; } interface None { readonly kind: 'none'; } // البناءات function some<T>(value: T): Option<T> { return { kind: 'some', value }; } function none(): Option<never> { return { kind: 'none' }; } // دوال مساعدة function isSome<T>(option: Option<T>): option is Some<T> { return option.kind === 'some'; } function isNone<T>(option: Option<T>): option is None { return option.kind === 'none'; } function getOrElse<T>(option: Option<T>, defaultValue: T): T { return isSome(option) ? option.value : defaultValue; } function mapOption<T, U>( option: Option<T>, fn: (value: T) => U ): Option<U> { return isSome(option) ? some(fn(option.value)) : none(); } // مثال على الاستخدام function findUserById(id: number): Option<User> { const user = database.find(id); return user ? some(user) : none(); } const userOption = findUserById(123); const userName = getOrElse( mapOption(userOption, user => user.name), 'مجهول' );

نمط حدود الأخطاء

لتطبيقات React مع تايب سكريبت، توفر حدود الأخطاء طريقة لالتقاط الأخطاء في أشجار المكونات:

import React, { Component, ErrorInfo, ReactNode } from 'react'; interface Props { children: ReactNode; fallback?: ReactNode; onError?: (error: Error, errorInfo: ErrorInfo) => void; } interface State { hasError: boolean; error?: Error; } class ErrorBoundary extends Component<Props, State> { constructor(props: Props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('خطأ تم التقاطه بواسطة الحد:', error); console.error('مكدس المكونات:', errorInfo.componentStack); this.props.onError?.(error, errorInfo); } render(): ReactNode { if (this.state.hasError) { return this.props.fallback || ( <div> <h2>حدث خطأ ما</h2> <p>{this.state.error?.message}</p> </div> ); } return this.props.children; } } // الاستخدام function App(): JSX.Element { return ( <ErrorBoundary fallback={<ErrorFallback />} onError={(error, errorInfo) => { logErrorToService(error, errorInfo); }} > <MainContent /> </ErrorBoundary> ); }

أخطاء الاتحاد المميز

استخدم الاتحادات المميزة لإنشاء تسلسلات هرمية للأخطاء آمنة من حيث النوع بدون فئات:

// أنواع الأخطاء كاتحاد مميز type ApiError = | { type: 'network'; message: string; retryable: true } | { type: 'validation'; fields: Record<string, string[]> } | { type: 'unauthorized'; requiredRole: string } | { type: 'notFound'; resource: string; id: string } | { type: 'server'; statusCode: number; message: string }; // معالجات الأخطاء مع فحص شامل function handleApiError(error: ApiError): string { switch (error.type) { case 'network': return \`خطأ في الشبكة: ${error.message}. جارٍ إعادة المحاولة...\`; case 'validation': return \`فشل التحقق: ${Object.keys(error.fields).join(', ')}\`; case 'unauthorized': return \`تم رفض الوصول. الدور المطلوب: ${error.requiredRole}\`; case 'notFound': return \`${error.resource} بمعرف ${error.id} غير موجود\`; case 'server': return \`خطأ في الخادم (${error.statusCode}): ${error.message}\`; default: // تايب سكريبت تضمن عدم الوصول إلى هنا أبداً const _exhaustive: never = error; return _exhaustive; } } // دوال المصنع لإنشاء الأخطاء const ApiErrors = { network: (message: string): ApiError => ({ type: 'network', message, retryable: true }), validation: (fields: Record<string, string[]>): ApiError => ({ type: 'validation', fields }), unauthorized: (requiredRole: string): ApiError => ({ type: 'unauthorized', requiredRole }), notFound: (resource: string, id: string): ApiError => ({ type: 'notFound', resource, id }), server: (statusCode: number, message: string): ApiError => ({ type: 'server', statusCode, message }) }; // الاستخدام const error = ApiErrors.validation({ email: ['تنسيق غير صالح'], password: ['قصير جداً'] }); console.log(handleApiError(error));
تحذير: لا تستخدم أبداً any لأنواع الأخطاء. استخدم دائماً unknown وحراس الأنواع لضمان أمان النوع في معالجة الأخطاء.

معالجة الأخطاء غير المتزامنة

دمج أنواع Result مع الدوال غير المتزامنة يوفر معالجة قوية للأخطاء:

// نوع Result غير متزامن type AsyncResult<T, E = Error> = Promise<Result<T, E>>; // مساعد لتغليف العمليات غير المتزامنة async function tryCatch<T>( fn: () => Promise<T> ): AsyncResult<T> { try { const value = await fn(); return ok(value); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } // مثال على الاستخدام async function fetchUserSafely(id: number): AsyncResult<User, ApiError> { const result = await tryCatch(async () => { const response = await fetch(\`/api/users/${id}\`); if (!response.ok) { throw ApiErrors.server(response.status, response.statusText); } return response.json(); }); if (!result.success) { if (result.error.message.includes('network')) { return err(ApiErrors.network(result.error.message)); } return err(ApiErrors.server(500, result.error.message)); } return ok(result.value); } // ربط العمليات غير المتزامنة async function getUserEmailSafely(id: number): AsyncResult<string, ApiError> { const userResult = await fetchUserSafely(id); if (!userResult.success) { return userResult; } return ok(userResult.value.email); }
تمرين:
  1. أنشئ تسلسلاً هرمياً للأخطاء المخصصة لتطبيق مدونة مع أخطاء: PostNotFound، UnauthorizedEdit، InvalidContent، وPublishError.
  2. نفذ عميل API قائم على Result يتعامل مع أخطاء الشبكة وأخطاء التحقق وأخطاء الخادم.
  3. أنشئ نظام تخزين مؤقت قائم على Option مع طرق get وset وdelete.
  4. ابنِ مسجل أخطاء آمن من حيث النوع يصنف الأخطاء حسب الخطورة (معلومات، تحذير، خطأ، حرج) باستخدام الاتحادات المميزة.
  5. نفذ مكون ErrorBoundary مع تايب سكريبت يسجل الأخطاء في خدمة خارجية ويعرض واجهات مستخدم احتياطية مختلفة بناءً على نوع الخطأ.

الخلاصة

معالجة الأخطاء في تايب سكريبت تتجاوز بكثير كتل try-catch. من خلال الاستفادة من الأخطاء المكتوبة، وفئات الأخطاء المخصصة، وأنماط Result وOption، والاتحادات المميزة، يمكنك بناء تطبيقات مع معالجة أخطاء صريحة وآمنة من حيث النوع. هذه الأنماط تجبرك على معالجة الأخطاء في وقت الترجمة، مما يقلل من المفاجآت في وقت التشغيل ويجعل كودك أكثر قابلية للصيانة وقوة.