تخطَّ إلى المحتوى

معالجة الأخطاء في JavaScript: try و catch و throw

الدرس 22 من 28 · JavaScript

مهما كان كودك متقناً، ستحدث أشياء خارجة عن إرادتك: ينقطع الإنترنت، ويرسل الخادم بيانات تالفة، ويكتب المستخدم حروفاً في خانة الأرقام. إذا لم تستعد لذلك، يتوقف برنامجك عند أول مشكلة، ويرى المستخدم صفحة مجمدة. في هذا الدرس ستتعلم كيف تتوقع الأخطاء وتتعامل معها بهدوء.

console.log("قبل الخطأ");
const user = null;
console.log(user.name);
console.log("بعد الخطأ");
الناتج
قبل الخطأ
TypeError: Cannot read properties of null (reading 'name')

السطر الأخير لم يُنفَّذ. عندما يقع خطأ بدون معالجة، يتوقف تنفيذ السكربت عند ذلك السطر، وكل ما بعده يُهمل. في صفحة ويب معناه أن الأزرار قد تتوقف عن الاستجابة.

try {
const data = JSON.parse("{ بيانات تالفة }");
console.log(data);
} catch (error) {
console.log("تعذرت قراءة البيانات");
}
console.log("البرنامج مستمر");
الناتج
تعذرت قراءة البيانات
البرنامج مستمر
  • try: ضع فيها الكود الذي قد يفشل.
  • catch: تُنفَّذ فقط إذا وقع خطأ داخل try. وإذا لم يقع خطأ تُتخطى.
  • بعد الكتلتين يكمل البرنامج طبيعياً.

تخيّل try مثل لاعب سيرك يمشي على الحبل، و catch هي الشبكة تحته: إذا مشى بسلام لم تُستخدم الشبكة، وإذا سقط التقطته وأكمل العرض.

انتبه: عند وقوع الخطأ يقفز التنفيذ فوراً إلى catch، وباقي أسطر try لا تُنفَّذ. لذلك لم يُطبع data في المثال.

المتغير بين قوسي catch كائن يحمل معلومات عن الخطأ:

try {
const price = 100;
price = 200;
} catch (error) {
console.log(error.name);
console.log(error.message);
}
الناتج
TypeError
Assignment to constant variable.
الخاصية معناها
error.name نوع الخطأ
error.message وصف نصي لما حدث
error.stack مسار الاستدعاءات التي أدت إلى الخطأ، ويفيد المبرمج في التتبع
النوع متى يحدث مثال
ReferenceError استخدام اسم غير معرّف console.log(total) و total غير موجود
TypeError عملية على نوع لا يدعمها null.name أو استدعاء شيء ليس دالة
SyntaxError نص لا يتبع القواعد JSON.parse لنص غير صالح
RangeError قيمة خارج المدى المسموح new Array(-1)

كتلة finally تُنفَّذ دائماً: نجح الكود أم فشل.

function loadData(text) {
console.log("إظهار دائرة التحميل");
try {
const data = JSON.parse(text);
console.log("وصلت البيانات:", data.title);
} catch (error) {
console.log("فشل التحميل");
} finally {
console.log("إخفاء دائرة التحميل");
}
}
loadData('{"title":"مرحباً"}');
loadData("نص تالف");
الناتج
إظهار دائرة التحميل
وصلت البيانات: مرحباً
إخفاء دائرة التحميل
إظهار دائرة التحميل
فشل التحميل
إخفاء دائرة التحميل

في الحالتين أُخفيت دائرة التحميل. finally مكان مناسب لكود التنظيف: إخفاء مؤشر التحميل، وإعادة تفعيل زر، وإغلاق اتصال.

JavaScript ترمي الأخطاء عند مخالفة قواعدها. لكن برنامجك له قواعده أيضاً: العمر لا يكون سالباً، والكمية لا تزيد على المخزون. تستطيع أن ترمي خطأ بنفسك عند مخالفتها:

function divide(a, b) {
if (b === 0) {
throw new Error("لا يمكن القسمة على صفر");
}
return a / b;
}
try {
console.log(divide(10, 2));
console.log(divide(5, 0));
console.log("هذا السطر لن يُنفَّذ");
} catch (error) {
console.log("خطأ:", error.message);
}
الناتج
5
خطأ: لا يمكن القسمة على صفر
  • throw توقف الدالة فوراً وترمي الخطأ إلى من استدعاها.
  • new Error("...") تنشئ كائن خطأ، والنص هو message.
  • الخطأ يصعد من دالة إلى التي استدعتها حتى يجد try و catch تلتقطه. وإن لم يجد توقف البرنامج.

التحقق من مدخلات المستخدم

Section titled “التحقق من مدخلات المستخدم”
function parseAge(input) {
const age = Number(input);
if (input.trim() === "") {
throw new Error("العمر مطلوب");
}
if (Number.isNaN(age)) {
throw new Error("اكتب العمر بالأرقام");
}
if (age < 0 || age > 120) {
throw new Error("العمر يجب أن يكون بين 0 و 120");
}
return age;
}
const inputs = ["14", "", "أربعة عشر", "200"];
for (const input of inputs) {
try {
console.log("عمر صحيح:", parseAge(input));
} catch (error) {
console.log("مرفوض:", error.message);
}
}
الناتج
عمر صحيح: 14
مرفوض: العمر مطلوب
مرفوض: اكتب العمر بالأرقام
مرفوض: العمر يجب أن يكون بين 0 و 120

لاحظ أن try و catch داخل الحلقة، ولذلك لم يوقف المدخل الخاطئ فحص باقي المدخلات.

لا تلفّ كل شيء بـ try. استخدمها مع ما هو خارج عن سيطرتك: بيانات من خادم، ومدخلات مستخدم، وتخزين المتصفح. أما أخطاء الكود نفسه، مثل اسم متغير خاطئ، فحلها أن تصلح الكود لا أن تخفي الخطأ.

وأسوأ ما تفعله هو catch فارغة:

try {
JSON.parse("نص تالف");
} catch (error) {
// لا شيء!
}

الخطأ ابتُلع بصمت. البرنامج لا يعمل كما يجب، ولا أحد يعرف السبب. على الأقل اطبع الخطأ بـ console.error(error).

جرّب بنفسكJavaScript
أو اضغط Ctrl + Enter

الناتج

أضف شرطاً ثالثاً يرفض السحب إذا زاد المبلغ على 300 في المرة الواحدة، وجرّبه.

  • كتلة catch فارغة: تخفي المشكلة ولا تحلها. اطبع الخطأ، أو أخبر المستخدم.
  • رمي نص بدل كائن خطأ: throw "خطأ" يعمل، لكنك تفقد message و stack. اكتب دائماً throw new Error("...").
  • وضع البرنامج كله في try واحدة: يصعب معرفة أي جزء فشل. اجعل try صغيرة حول الجزء الخطر فقط.

سؤال

متى تُنفَّذ كتلة finally؟

سؤال

ما الطريقة الصحيحة لرمي خطأ خاص؟

  • الخطأ غير المعالج يوقف السكربت عند سطره.
  • try للكود الخطر، و catch لما تفعله عند الفشل، و finally لما يُنفَّذ دائماً.
  • كائن الخطأ فيه name و message و stack.
  • throw new Error("...") ترمي خطأك الخاص عند مخالفة قواعد برنامجك.
  • استخدم try مع ما هو خارج عن سيطرتك، ولا تترك catch فارغة.