مقدمة: لماذا معالجة الأخطاء في TypeScript ليست مجرد try/catch
يضيف TypeScript الكتابة الثابتة إلى JavaScript، لكن أخطاء وقت التشغيل (runtime errors) تظل جزءًا من أي تطبيق. على عكس JS الخالص، حيث catch(e) يُرجع any، يقدم TypeScript آليات أكثر صرامة. المعالجة الصحيحة للأخطاء ليست فقط لمنع الأعطال، بل أيضًا لإنشاء نظام واضح لتصحيح الأخطاء وصيانة الكود. في هذه المقالة، سنغطي جميع الجوانب: من try/catch الأساسي إلى إنشاء تسلسلات هرمية مخصصة للأخطاء مع كتابة كاملة.
1. بناء الجملة الأساسي لـ try/catch ومشكلة النوع unknown
التركيب الكلاسيكي
يبدو التركيب الأساسي في TypeScript كما هو في JavaScript:
try { // كود قد يرمي استثناءً const data = JSON.parse('{"name": "TypeScript"}'); console.log(data.name);} catch (error) { // معالجة الخطأ console.error('حدث خطأ:', error);}لكن هناك فرق مهم: في TypeScript، المتغير error في كتلة catch له النوع unknown (وليس any كما في JavaScript، ما لم يُذكر خلاف ذلك). هذا من أجل أمان الأنواع.
مشكلة unknown وكيفية حلها
النوع unknown يعني أنه لا يمكننا ببساطة الوصول إلى خصائص الخطأ، مثل error.message. سيعطي المترجم خطأ:
catch (error) { // خطأ TS2571: Object is of type 'unknown'. console.log(error.message); // ❌ لن يتم تجميعه}للوصول إلى الخصائص، نحتاج إلى تضييق النوع:
catch (error) { if (error instanceof Error) { console.log(error.message); // ✅ يعمل } else { console.log('خطأ غير معروف:', error); }}أو استخدام التحقق من وجود الخاصية:
catch (error) { if (error && typeof error === 'object' && 'message' in error) { console.log((error as { message: string }).message); }}2. إنشاء فئات أخطاء مخصصة مع كتابة الأنواع
لماذا هذا مهم؟
يحتوي Error القياسي فقط على name و message و stack. في المشاريع الحقيقية، غالبًا ما نحتاج إلى حقول إضافية: كود الخطأ، حالة HTTP، بيانات للتسجيل. يسمح TypeScript بإنشاء تسلسل هرمي لفئات الأخطاء مع دعم كامل للأنواع.
مثال لفئة مخصصة
// الفئة الأساسية لجميع أخطاء التطبيقclass AppError extends Error { public readonly statusCode: number; public readonly isOperational: boolean;
constructor(message: string, statusCode: number = 500, isOperational: boolean = true) { super(message); this.name = 'AppError'; this.statusCode = statusCode; this.isOperational = isOperational; // استعادة النموذج الأولي (ضروري لـ TypeScript) Object.setPrototypeOf(this, new.target.prototype); }}
// أخطاء متخصصةclass NotFoundError extends AppError { constructor(resource: string) { super(`${resource} غير موجود`, 404); this.name = 'NotFoundError'; }}
class ValidationError extends AppError { public readonly errors: string[];
constructor(errors: string[]) { super('خطأ في التحقق', 400); this.name = 'ValidationError'; this.errors = errors; }}الاستخدام مع try/catch
function getUser(id: number): User { if (id <= 0) { throw new ValidationError(['يجب أن يكون المعرف رقمًا موجبًا']); } // ... منطق الحصول على المستخدم if (!user) { throw new NotFoundError('المستخدم'); } return user;}
try { const user = getUser(-1);} catch (error) { if (err