يستخدم TypeScript كلمات JavaScript نفسها: try وcatch وfinally وthrow. الجزء الخاص بـ TypeScript هو متغير catch: مع تفعيل strict يكون نوعه unknown، فتفحص ما الذي رُمي قبل استخدامه.
جانب وقت التشغيل (كيف يُفك المكدس، وما أنواع الأخطاء المدمجة الموجودة) مشروح في try/catch في JavaScript.
لماذا يكون متغير catch من النوع unknown
تستطيع JavaScript رمي أي شيء: Error، أو نص، أو رقم، أو undefined، أو كائن من مكتبة ما. لا يستطيع TypeScript معرفة أيها، لذلك مع strict (الخيار useUnknownInCatchVariables) يكون المتغير unknown ويجب أن تضيّقه قبل الاستخدام:
index.ts(5,34): error TS18046: 'e' is of type 'unknown'.
دون strict يكون e من النوع any ويُترجم الكود نفسه، ثم يخطئ وقت التشغيل عندما يُرمى نص: تصبح e.message قيمتها undefined، وأي وصول أعمق، مثل e.message.length، يرمي TypeError. ولا يمكنك الخروج من ذلك بتحديد النوع أيضًا: catch (e: Error) خطأ TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).
تضييق نوع الخطأ
يغطي instanceof Error كل ما بُني على Error، بما في ذلك TypeError وSyntaxError وRangeError وأصنافك الفرعية. لأي شيء آخر، ارجع إلى تحويل القيمة إلى نص. دالة مساعدة صغيرة تُبقي مواضع الاستدعاء قصيرة:
كتلة catch التي تعالج بعض الأخطاء فقط يجب أن تعيد رمي البقية: if (!(e instanceof ValidationError)) throw e;. ابتلاع الأخطاء غير المعروفة يخفي أخطاء برمجية حقيقية.
رمي الأخطاء
يقبل throw أي تعبير، ولا يقيّده TypeScript. ومع ذلك ارمِ كائنات Error: فهي تحمل تتبع المكدس، وكل فحص instanceof Error في الكود يعتمد عليها.
الدالة التي ترمي دائمًا نوع إعادتها never، ويستخدمه TypeScript للتضييق بعد الاستدعاء:
منذ ES2022 يأخذ Error وسيطًا ثانيًا فيه cause، يربط خطأً منخفض المستوى بالخطأ الذي ترميه: throw new Error("could not load settings", { cause: e }). ويستطيع المستدعي قراءة err.cause (نوعه unknown).
أصناف الأخطاء المخصصة
الصنف الفرعي من Error يتيح للمستدعين التمييز بين حالات الفشل بـ instanceof وحمل بيانات إضافية. حدد name، لأن القيمة الموروثة هي "Error" وهي تظهر في السجلات وفي String(err):
افحص الصنف الأكثر تحديدًا أولًا، لأن NotFoundError هو أيضًا HttpError. والمعدِّل override اختياري هنا ما لم يكن noImplicitOverride مفعّلًا.
تضيف الأدلة القديمة Object.setPrototypeOf(this, new.target.prototype) إلى كل مُنشئ خطأ. كان ذلك ضروريًا عند ترجمة الأصناف إلى دوال ES5، حيث كان instanceof يتعطل مع الأصناف الفرعية من Error. لم يعد TypeScript 7 يدعم target: "es5" (يبلّغ عن TS5108: Option 'target=ES5' has been removed)، ومع ES2015 أو ما بعده يعمل class ... extends Error الأصلي، كما في المثال.
نمط Result
لا يتتبع TypeScript الأخطاء التي قد ترميها الدالة، فلا شيء يذكّر المستدعين بمعالجتها. لحالات الفشل المتوقعة (مُدخل غير صالح، سجل غير موجود)، إعادة قيمة تقول "نجاح أو فشل" تُدخل الخطأ في نظام الأنواع:
قراءة r.value قبل فحص r.ok خطأ ترجمة، وهذا هو المقصود. استخدم الاستثناءات لغير المتوقع (أخطاء برمجية، اتصال مقطوع)، وقيم Result للنتائج التي يجب أن يقرر المستدعي بشأنها.
الأخطاء في الكود غير المتزامن
الـ promise المرفوض يصبح خطأ مرميًا عند await، لذلك يعمل try/catch حول await بالطريقة نفسها، ويكون المتغير مرة أخرى unknown. الفرق الوحيد هو .catch() على promise: معامل دالتها نوعه any لا unknown، لذلك حدد نوعه بنفسك (.catch((e: unknown) => ...)). راجع async/await لأمثلة.
أخطاء شائعة
- قراءة
e.messageدون تضييق. معstrictلا يُترجم الكود؛ ودونه يتعطل عند رمي قيم ليستError. - التقاط كل شيء والاستمرار. عالج الأخطاء التي تتوقعها وأعد رمي البقية.
- نسيان
nameفي الأخطاء المخصصة. عندها تقول السجلاتErrorلكل الأخطاء. - رمي النصوص.
throw "failed"ليس فيه تتبع للمكدس ويفشل في فحوصinstanceof Error.
الأسئلة الشائعة
ما نوع الخطأ في كتلة catch في TypeScript؟
unknown عندما يكون strict مفعّلًا (الخيار useUnknownInCatchVariables)، وإلا فهو any. تستطيع JavaScript رمي أي قيمة، لا كائنات Error فقط، لذلك يلزمك TypeScript بالفحص. ضيّقه بـ if (e instanceof Error) قبل قراءة e.message.
هل يمكن تحديد نوع متغير catch كـ Error في TypeScript؟
لا. catch (e: Error) خطأ TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. المسموح فقط unknown وany، لأنه لا شيء يضمن ما الذي رُمي. ضيّق النوع داخل الكتلة بدلًا من ذلك.
كيف أرمي خطأ في TypeScript؟
كما في JavaScript: throw new Error("message")، أو نسخة من صنف فرعي مدمج أو مخصص مثل new RangeError(...). يسمح لك TypeScript برمي أي قيمة، لكن رمي كائنات Error يحفظ تتبع المكدس ويجعل فحوص instanceof Error تعمل.
كيف أنشئ صنف خطأ مخصصًا في TypeScript؟
ورّث من Error، واستدعِ super(message, options)، وحدد name: class NotFoundError extends Error { name = "NotFoundError"; }. الحقول الإضافية توضع في المُنشئ. مع أي target مدعوم (ES2015 وما بعده) يعمل instanceof NotFoundError دون أي إصلاح للـ prototype.
هل في TypeScript استثناءات مفحوصة أو عبارة throws؟
لا. نوع الدالة لا يقول شيئًا عما قد ترميه، ولا يتحقق المترجم أبدًا من أن الأخطاء معالجة. للأخطاء التي يُتوقع من المستدعي معالجتها، أعدها كقيم بنوع Result (discriminated union للنجاح والفشل) حتى يفحصها نظام الأنواع فعلًا.