اذهب إلى المحتوى

التعامل مع الأخطاء، جرب... التقط try..catch في جافاسكربت


صفا الفليج

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

في العادة ”يموت“ السكربت (أي يتوقّف مباشرة) لو حدث خطأ، ويطبع ذلك في الطرفية، لكن في كل الأحوال يمكن التعامل مع هذا الخطأ بعدة طرق.

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

تتيح لنا الصياغة try..catch إمكانية ”التقاط“ هذه الأخطاء والقيام بما هو مفيد بدل أن يموت السكربت.

صياغة try..catch

للتعبير try..catch كتلتين برمجيتين أساسيتين: التجربة try والالتقاط catch بعدها:

try {

  // الشيفرة...

} catch (err) {

  // التعامل مع الأخطاء

}

يعمل التعبير هكذا:

  1. أولًا، يجري تنفيذ الشيفرة في try {...}‎.
  2. لم نجح التنفيذ دون أيّ أخطاء، تُهمل الكتلة catch(err)‎ وتصل عملية التنفيذ إلى نهاية try وتواصل عملها بعد تجاهل catch.
  3. إن حدث أيّ خطأ يتوقّف التنفيذ داخل كتلة try وينتقل سير العملية إلى بداية الكتلة catch(err)‎. سيحتوي المتغير err (يمكننا استبداله بأيّ اسم آخر) كائن خطأ فيه تفاصيل عمّا حدث.

try-catch-flow.png

بهذا لا تقتل الأخطاء داخل كتلة try {…}‎ السكربت، فما زال ممكنًا أن نتعامل معها في catch.

وقت الأمثلة.

  • إليك مثالًا ليس فيه أخطاء: يعرض alert السطرين (1) و (2):

    try {
    
      alert('Start of try runs');  // (1) <-- بداية التشغيلات
    
      // ...ما من أخطاء هنا
    
      alert('End of try runs');   // (2) <-- نهاية التشغيلات
    
    } catch(err) {
       // (3)‫  تُهمل catch إذ ليست هناك أخطاء
      alert('Catch is ignored, because there are no errors');
    
    }
  • مثال فيه خطأ: يعرض السطرين (1) و(3):

    try {
    
      alert('Start of try runs');  // (1) <-- بداية التشغيلات
    
      lalala; // error, variable is not defined! خطأ: المتغير غير معرّف!
    
      alert('End of try (never reached)');  // (2)‫ نهاية كتلة try (لا نصل إليها أبدًا)
    
    } catch(err) {
    
      alert(`Error has occurred!`); // (3) <-- حدث خطأ!
    
    }

تحذير: لا يعمل تعبير try..catch إلّا مع الأخطاء أثناء التشغيل يجب أن تكون الشيفرة البرمجية صياغتها صحيحة لكي تعمل try..catch بعبارة أخرى، يجب أن تكون الشيفرة البرمجية خالية من أخطاء الصياغة.

لن تعمل في هذه الحالة لأن هنالك أقواسًا مفتوحة بدون غُلاقاتها.

try {
  {{{{{{{{{{{{
} catch(e) {
  alert("لا يمكن للمحرك فهم هذه الشيفرة فهي غير صحيحة");
}

أولًا يقرأ محرك جافاسكربت الشيفرة البرمجية، ومن ثمّ يشغّلها. تدعى الأخطاء التي تحدث في مرحلة القراءة أخطاء التحليل (parse-time) ولا يمكن إصلاحها (من داخل الشيفرة البرمجية نفسها). وذلك لأن محرك لغة جافاسكربت لا يستطيع فهم الشيفرة البرمجية من الأساس. تحذير:تعمل الصياغة try..catch بشكل متزامن. إذا حدث خطأ ما في شيفرة برمجية مجدولة مثلما في setTimeout فلن تستطيع الصياغة try..catch أن تلتقطه:

try {
  setTimeout(function() {
    noSuchVariable; // السكربت سيموت هنا
  }, 1000);
} catch (e) {
  alert( "won't work" );
}

وذلك لأن التابِع سيُنفذ لاحقًا، بينما يكون محرك جافاسكربت غادر باني try..catch.

للتقاط خطأ بداخل تابِع مُجدوَل يجب أن تكون الصياغة try..catch في داخل هذا التابِع.

setTimeout(function() {
  try {    
// صياغة ‫try..catch تُعالح الخطأ 
    noSuchVariable; // try..catch handles the error!
  } catch {
    alert( "error is caught here!" );
  }
}, 1000);

كائن الخطأ Error

تولّد جافاسكربت -متى حدث الخطأ- كائنًا يحوي تفاصيل الخطأ كاملةً، بعدها تُمرّرها وسيطًا إلى catch:

try {
  // ...
} catch(err) { // ‫ هذا ”كائن الخطأ“، ويمكننا استعمال أيّ اسم نريد بدل err
  // ...
}

لكائن الخطأ (في حالة الأخطاء المضمّنة في اللغة) خاصيتين اثنتين:

  • name: اسم الخطأ. فمثلًا لو كان المتغير غير معرّفًا فسيكون الاسم "ReferenceError".

  • message: الرسالة النصية بتفاصيل الخطأ.

كما هناك خاصيات أخرى غير قياسية في أغلب بيئات جافاسكربت. أكثرها استعمالًا ودعمًا هي:

  • stack: مَكدس الاستدعاء الحالي: سلسلة نصية فيها معلومات عن سلسلة الاستدعاءات المتداخلة التي تسبّبت بالخطأ. نستعمل الخاصية للتنقيح فقط.

مثال:

try {
*!*
  lalala; // خطأ المتغيّر غير معرّف
*/!*
} catch(err) {
  alert(err.name); // ReferenceError(خطأ في الإشارة)
  alert(err.message); // المتغيّر ‫lalala غير معرف
  alert(err.stack); // ‫ReferenceError:(خطأ في الإشارة) المتغيّر ‫lalala غير معرّف في (...call stack)

  //يمكننا أيضًا عرض الخطأ بالكامل
  //تحول الخطأ إلى سلسلة نصية هكذا ‫:"name: message"

  alert(err); // ‫ReferenceError:(خطأ في الإشارة) المتغيّر ‫lalala غير معرّف
}

إسناد "catch" الاختياري

تحذير:هذه إضافة حديثة للغة من الممكن أن تحتاج لترقيع الخطأ في المتصفحات القديمة لأن هذه الميّزة جديدة.

لو لم تريد تفاصيل الخطأ فيمكنك إزالتها من catch:

try {
  // ...
} catch { // <-- بدون المتغير (err)
  // ...
}

استعمال "try..catch"

هيًا نرى معًا مثالًا واقعيًا عن try..catch:

كما نعلم فجافاسكربت تدعم التابِع JSON.parse(str)‎ ليقرأ القيم المرمّزة بِـ JSON.

عادةً ما نستعمله لفكّ ترميز البيانات المستلمة من الشبكة، كانت آتية من خادوم أو من أيّ مصدر غيره.

نستلم البيانات ونستدعي JSON.parse هكذا:

let json = '{"name":"John", "age": 30}'; // بيانات من الخادوم

// لاحظ
let user = JSON.parse(json); // نحوّل التمثيل النصي إلى كائن جافاسكربت

// الآن صار ‫user كائنًا فيه خاصيات أتت من السلسلة النصية
alert( user.name ); // John
alert( user.age );  // 30

يمكنك أن ترى تفاصيل أكثر عن JSON في درس JSON methods, toJSON.

لو كانت سلسلة json معطوبة، فسيُولّد JSON.parse خطأً و”ويموت“ السكربت.

هل نقبل بالأمر الواقع المرير؟ لا وألف لا!

في هذا الحال لن يعرف الزائر ما يحدث لو حدث للبيانات أمر (ما لم يفتح طرفية المطوّرين). وعادةً ما لا يحب الناس ما ”يموت“ أمامهم دون أن يُعطيهم رسالة خطأ.

فلنستعمل try..catch للتعامل مع هذه المعضلة:

let json = "{ bad json }";

try {

  let user = JSON.parse(json); // <-- لو حدث خطأ...
  alert( user.name ); // فلن يعمل هذا

} catch (e) {
  // ...تنتقل عملية التنفيذ إلى هنا
  alert( "Our apologies, the data has errors, we'll try to request it one more time." ); // المعذرة، في البيانات أخطاء. سنحاول طلبها مرة ثانية.
  alert( e.name );
  alert( e.message );
}

استعملنا هنا الكتلة catch لعرض رسالة لا أكثر، ولكن يمكن أن نستغلّها أفضل من هذا: مثل إرسال طلب إلى الشبكة، أو اقتراح عملية بديلة على الزائر أو إرسال معلومات الخطأ إلى … تسجيل، وغيرها… هذا كلّه أفضل من الموت والموت فقط.

رمي أخطائنا نحن

ماذا لو كان كائن json صحيح صياغيًا إلّا أنّ الخاصية المطلوبة name ليست فيه؟ هكذا مثلًا:

let json = '{ "age": 30 }'; // البيانات ناقصة

try {

  let user = JSON.parse(json); // <-- لا أخطاء
  alert( user.name ); // ما من اسم!

} catch (e) {
  alert( "doesn't execute" ); // لا يتنفّذ السطر
}

هنا سيعمل التابِع JSON.parse كما يجب، لكن عدم وجود الخاصية name هي المشكلة والخطأ في هذه الحالة.

لتوحيد طريقة معالجة الأخطاء، سنستخدم مُعامل throw.

مُعامل "الرمي" throw

يُولّد لنا مُعامل الرمي throw خطأً.

صياغته هي:

throw <error object>

يمكننا تقنيًا استعمال ما نريد ليكون كائن خطأ، مثل الأنواع الأولية كالأعداد والسلاسل النصية. ولكن من الأفضل استعمال الكائنات ومن الأفضل أكثر أن تملك خاصيتي الاسم name والرسالة message (لتكون متوافقة إلى حدٍّ ما مع الأخطاء المضمّنة).

في جافاسكربت مختلف البانيات المضمّنة للأخطاء القياسية: الخطأ Error والخطأ الصياغي SyntaxError والخطأ في الإشارة ReferenceError والخطأ في النوع TypeError وغيرها. يمكننا استعمال هذه البانيات أيضًا لإنشاء كائنات الخطأ.

صياغتها هي:

let error = new Error(message);
// أو
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...

في الأخطاء المضمّنة في اللغة (ولا أعني الكائنات أيًا كانت، الأخطاء بعينها) تكون قيمة الخاصية name هي ذاتها اسم الباني، وتُؤخذ الرسالة message من الوسيط.

مثال:

let error = new Error("Things happen o_O"); // غرائب وعجائب ة_ه

alert(error.name); // خطأ
alert(error.message); // غرائب وعجائب ة_ه

لنرى نوع الخطأ الذي يُولّده التابِع JSON.parse:

try {
  JSON.parse("{ bad json o_O }"); // جيسون شقي ة_ه
} catch(e) {
  alert(e.name); // SyntaxError خطأ صياغي
  alert(e.message); // Unexpected token o in JSON at position 2
}

كما رأينا فهو خطأ صياغي SyntaxError.

وفي حالتنا نحن فعدم وجود الخاصية name هي خطأ، إذ لا بدّ أن يكون للمستخدم اسمًا.

هيًا نرمِ الخطأ:

let json = '{ "age": 30 }'; // البيانات ناقصة

try {

  let user = JSON.parse(json); // <-- لا أخطاء

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name"); // (*) البيانات ناقصة: ليس هنالك اسم
  }

  alert( user.name );

} catch(e) {

alert( "JSON Error: " + e.message ); // خطأ ‫JSON: البيانات ناقصة: ليس هنالك اسم
}

يُولّد مُعامل throw (في السطر (*)) خطأً صياغيًا SyntaxError له الرسالة الممرّرة message، تمامًا كما تُولّد جافاسكربت نفسها الخطأ. بهذا يتوقّف التنفيذ داخل try وينتقل سير العملية إلى catch.

الآن صارت الكتلة catch هي المكان الذي نتعامل فيه مع الأخطاء فحسب، أكانت أخطاء JSON.parse أم أخطاء أخرى.

إعادة الرمي

يمكننا في المثال أعلاه استعمال try..catch للتعامل مع البيانات الخاطئة. ولكن هل يمكن أن يحدث خطأ آخر غير متوقّع في كتلة try {...}‎؟ مثلًا لو كان خطأ المبرمج (لم يعرّف المتغير) أو شيئًا آخر ليس أنّ ”البيانات خاطئة“؟

مثال:

let json = '{ "age": 30 }'; // البيانات ناقصة

try {
  user = JSON.parse(json); // ‫ نسينا ”let“ قبل user

  // ...
} catch(err) {

  alert("JSON Error: " + err);   // خطأ ‫JSON: خطأ في الإشارة: user غير معرّف (في الواقع، ليس خطأ JSON)

}

كلّ شيء ممكن كما تعلم! فالمبرمجون يخطؤون. حتّى في الأدوات مفتوحة المصدر والتي يستعملها ملايين البشر لسنين تمضي، لو اكتُشفت علة فجأةً ستؤدّي إلى اختراقات مأساوية.

في حالتنا هذه على try..catch التقاط أخطاء ”البيانات الخاطئة“ فقط، ولكنّ طبيعة catch هي أن تلتقط كلّ الأخطاء من try. فهنا مثلًا التقطت خطأً غير متوقع ولكنها ما زالت تصرّ على رسالة "JSON Error". هذا ليس صحيحًا ويصعّب من تنقيح الشيفرة كثيرًا.

لحسن الحظ فيمكننا معرفة الخطأ الذي التقطناه، من اسمه name مثلًا:

try {
  user = { /*...*/ };
} catch(e) {
  alert(e.name); // ‫”خطأ في الإشارة” محاولة الوصول لمتغيّر غير معرّف
}

القاعدة ليست بالمعقّدة:

على catch التعامل مع الأخطاء التي تعرفها فقط، و”إعادة رمي“ ما دونها.

يمكننا توضيح فكرة ”إعادة الرمي“ بهذه الخطوات:

  1. تلتقط catch كلّ الأخطاء.
  2. نحلّل كائن الخطأ err في كتلة catch(err) {...}‎.
  3. لو لم نعرف طريقة التعامل معه، رميناه throw err.

في الشيفرة أسفله استعملنا إعادة الرمي لتتعامل الكتلة catch مع أخطاء الصياغة فقط SyntaxError:

let json = '{ "age": 30 }'; // البيانات ناقصة
try {

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name"); // البيانات ناقصة: لا اسم
  }

  blabla(); // خطأ غير متوقع

  alert( user.name );

} catch(e) {

  if (e.name == "SyntaxError") {
    alert( "JSON Error: " + e.message ); // خطأ JSON: كذا كذا
  } else {
    throw e; // نُعيد رميه (*)
  }

}

الخطأ المرمي في السطر (*) داخل كتلة catch ”يسقط خارج أرض“ try..catch ويمكننا إمّا التقاطه باستعمال تعبير try..catch خارجي (لو كتبناه) أو سيقتل الخطأ السكربت.

هكذا لا تتعامل الكتلة catch إلا مع ما تعرف مع أخطاء وتتخطّى (إن صحّ القول) الباقي.

يوضّح المثال أسفله كيفية التقاط هذه الأخطاء بمستويات أخرى من try..catch:

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // خطأ!
  } catch (e) {
    // ...
    if (e.name != 'SyntaxError') {
      throw e; // ‫نُعيد رميه (لا ندري طريقة التعامل معه)
    }
  }
}

try {
  readData();
} catch (e) {
  alert( "External catch got: " + e ); // التقطناه أخيرًا!
}

هكذا لا تعرف الدالة readData إلّا طريقة التعامل مع أخطاء الصياغة SyntaxError، بينما تتصرّف تعابير try..catch الخارجية بغيرها من أخطاء (إن عرفتها).

try..catch..finally

لحظة… لم ننتهي بعد.

يمكن أيضًا أن يحتوي تعبير try..catch على مُنغلقة أخرى: finally.

لو كتبناها فستعمل في كلّ الحالات الممكنة:

  • بعد try لو لم تحدث أخطاء
  • وبعد catch لو حدثت أخطاء.

هكذا هي الصياغة ”الموسّعة“:

try {
   ... نحاول تنفيذ الشيفرة ...
} catch(e) {
   ... نتعامل مع الأخطاء ...
} finally {
   ... ننفّذه مهما كان الحال ...
}

جرّب تشغيل هذه الشيفرة:

try {
  alert( 'try' );
  if (confirm('Make an error?')) BAD_CODE(); // أتريد ارتكاب خطأ؟
} catch (e) {
  alert( 'catch' ); // أمسكناه
} finally {
  alert( 'finally' ); // بعد ذلك
}

يمكن أن تعمل الشيفرة بطريقتين اثنتين:

  1. لو كانت الإجابة على ”أتريد ارتكاب خطأ؟“ ”نعم“، فستعمل هكذا try -> catch -> finally.
  2. لو رفضت ذلك، فستعمل هكذا try -> finally.

نستعمل عادةً مُنغلقة finally متى ما شرعنا في شيء وأردنا إنهائه مهما كانت نتيجة الشيفرة.

فمثلًا نريد قياس الوقت الذي تأخذه دالة أعداد فيبوناتشي fib(n)‎. الطبيعي أن نبدأ القياس قبل التشغيل ونُنهيه بعده، ولكن ماذا لو حدث خطأ أثناء استدعاء الدالة؟ مثلًا خُذ شيفرة الدالة fib(n)‎ أسفله وسترى أنّها تُعيد خطأً لو كانت الأعداد سالبة أو لم تكن أعدادًا صحيحة (not integer).

مُنغلقة finally هي المكان الأمثل لإنهاء هذا القياس مهما كانت النتيجة، فتضمن لنا بأنّ الوقت سيُقاس كما ينبغي في الحالتين معًا، نجح تنفيذ أم لم ينجح وأدّى لخطأ:

let num = +prompt("Enter a positive integer number?", 35)

let diff, result;

function fib(n) {
  if (n < 0 || Math.trunc(n) != n) {
    throw new Error("Must not be negative, and also an integer.");
  }
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
  result = fib(num);
} catch (e) {
  result = 0;
*!*
} finally {
  diff = Date.now() - start;
}
*/!*

alert(result || "error occurred");

alert( `execution took ${diff}ms` );

يمكنك التأكّد بتشغيل الشيفرة وإدخال 35 في prompt، وستعمل كما يجب، أي تكون finally بعد try. وبعد ذلك جرّب إدخال ‎-1 وسترى خطأً مباشرةً وسيأخذ تنفيذ الشيفرة مدّة 0ms، وكِلا الزمنين المقيسين صحيحين.

أي أنّه يمكن للدالة أن بعبارة return أو throw، لا مشكلة إذ ستُنفّذ مُنغلقة finally في أيّ من الحالتين.

ملاحظة:المتغيّرات في صياغة try..catch..finally محلية. لاحظ أن المتغيرات result و diff في الشيفرة البرمجية أعلاه معرّفة قبل الصياغة try..catch.

وبذلك إذا عرفنا let في الصياغة try فستبقى مرئية بداخلها فقط.

ملاحظة: بخصوص finally و return فإن مُنغلقة finally تعمل مع أي خروج من صياغة try..catch والتي تتضمن خروج واضح من خلال تعليمة return.

في المثال أدناه هنالك تعليمة return في try في هذه الحالة ستنفذّ finally قبل عودة عنصر التحكم إلى الشيفرة البرمجية الخارجية مباشرة.

function func() {

  try {
*!*
    return 1;
*/!*

  } catch (e) {
    /* ... */
  } finally {
*!*
    alert( 'finally' );
*/!*
  }
}

alert( func() ); // ‫أولًا ستعمل تعليمة alert من المنغلقة finally ومن ثمّ هذه

ملاحظة: إن باني try..finally بدون منغلقة catch مفيد أيضًا إذ يمكننا استخدامه عندما لا نريد معالجة الأخطاء وإنما نريد التأكد من أن العمليات التي بدأناها انتهت فعلًا.

function func() {
// ‫نبدأ بشيء يحتاج لإكمال (مثل عملية القياس)‎

  try {
    // ...
  } finally {
    // نكمله هنا حتى وإن كلّ السكربت مات
  }
}

في الشيفرة البرمجية أعلاه، هنالك خطأ دائمًا يحدث في try لأنه لا يوجد catch. ومع ذلك ستعمل finally قبل الخروج من التابع.

الالتقاط العمومي

تحذير: مخصص لبيئة العمل إن المعلومات في هذه الفقرة ليست جزءًا من لغة جافاسكربت

لنقل مثلًا أنْ حصل خطأ فادح خارج تعبير try..catch، ومات السكربت (مثلًا كان خطأ المبرمج أو أيّ شيء آخر مريب).

أهناك طريقة يمكننا التصرّف فيها في هكذا حالات؟ لربّما أردنا تسجيل الخطأ أو عرض شيء معيّن على المستخدم (عادةً لا يرى المستخدمون رسائل الأخطاء) أو غيرها.

لا نجد في المواصفة أيّ شيء عن هذا، إلّا أنّ بيئات اللغة تقدّم هذه الميزة لأهميتها البالغة. فمثلًا تقدّم Node.js الحدث process.on("uncaughtException")‎. ولو كنّا نُطوّر للمتصفّح فيمكننا إسناد دالة إلى خاصية window.onerror الفريدة (إذ تعمل متى حدث خطأ لم يُلتقط).

الصياغة هي:

window.onerror = function(message, url, line, col, error) {
  // ...
};
  • message: رسالة الخطأ.

  • url: عنوان URL للسكربت أين حدث الخطأ.

  • line وcol: رقم السطر والعمود أين حدث الخطأ.

  • error: كائن الخطأ.

مثال:

<script>

  window.onerror = function(message, url, line, col, error) {
    alert(`${message}\n At ${line}:${col} of ${url}`);
  };


  function readData() {
    badFunc(); // Whoops, something went wrong!
  }

  readData();
</script>

عادةً، لا يكون الهدف من …. العمومي window.onerror استعادة تنفيذ السكربت (إذ هو أمر مستحيل لو كانت أخطاء من المبرمج) بل يكون لإرسال هذه الرسائل إلى المبرمج.

كما أنّ هناك خدمات على الوِب تقدّم مزايا لتسجيل الأخطاء في مثل هذه الحالات، أمثال https://errorception.com وhttp://www.muscula.com.

وهكذا تعمل:

  1. نُسجّل في الخدمة ونأخذ منهم شيفرة جافاسكربت صغيرة (أو عنوانًا للسكربت) لنضعها في صفحاتنا.
  2. يضبط هذا السكربت دالة window.onerror مخصّصة.
  3. ومتى حدث خطأ أرسلَ طلب شبكة بالخطأ إلى الخدمة.
  4. ويمكننا متى أردنا الولوج إلى واجهة الوِب للخدمة ومطالعة تلك الأخطاء.

خلاصة

يتيح لنا تعبير try..catch التعامل مع الأخطاء أثناء تنفيذ الشيفرات. كما يدلّ اسمها فهي تسمح ”بتجربة“ تنفيذ الشيفرة و”والتقاط“ الأخطاء التي قد تحدث فيها.

صياغتها هي:

try {
  // شغّل الشيفرة
} catch(err) {
  // انتقل إلى هنا لو حدث خطأ
  // ‫err هو كائن الخطأ
} finally {
  // مهما كان الذي حدث، نفّذ هذا
}

يمكننا إزالة قسم catch أو قسم finally، واستعمال الصيغ الأقصر try..catch وtry..finally.

لكائنات الأخطاء الخاصيات الآتية:

  • message: رسالة الخطأ التي نفهمها نحن البشر.
  • name: السلسلة النصية التي فيها اسم الخطأ (اسم باني الخطأ).
  • stack (ليست قياسية ولكنّها مدعومة دعمًا ممتازًا): المَكدس في لحظة إنشاء الخطأ.

لو لم تُرد كائن الخطأ فيمكنك إزالتها باستعمال catch { بدل catch(err) {‎.

يمكننا أيضًا توليد الأخطاء التي نريد باستعمال مُعامل throw. تقنيًا يمكن أن يكون وسيط throw ما تريد ولكن من الأفضل لو كان كائن خطأ يرث صنف الأخطاء Error المضمّن في اللغة. سنعرف المزيد عن توسعة الأخطاء في القسم التالي.

كما يُعدّ نمط ”إعادة الرمي“ البرمجي نمطًا مهمًا عند التعامل مع الأخطاء، فلا تتوقّع كتلة catch إلّا أنواع الأخطاء التي تفهمها وتعرف طريقة التعامل معها، والباقي عليها إعادة رميه لو لم تفهمه.

حتّى لو لم نستعمل تعبير try..catch فأغلب البيئات تتيح لنا إعداد … أخطاء ”عمومي“ لالتقاط الأخطاء التي ”تسقط أرضًا“. هذا … في المتصفّحات اسمه window.onerror.

تمارين

أخيرًا أم الشيفرة؟

الأهمية: 5

وازِن بين الشيفرتين هتين.

  1. تستعمل الأولى finally لتنفيذ الشيفرة بعد try..catch:

    try {
      عمل عمل عمل
    } catch (e) {
      نتعامل مع الأخطاء
    } finally {
      ننظّف مكان العمل // هنا
    }

     

  2. الثانية تضع عملية التنظيف بعد try..catch:

    try {
      عمل عمل عمل
    } catch (e) {
      نتعامل مع الأخطاء
    }
    
    ننظّف مكان العمل // ‎هنا

    سنحتاج بكل بتأكيد للتنظيف بعد العمل. بغض النظر إن وجدَنا خطأً ما أم لا.

هل هنالك ميزة إضافية لاستخدام finally؟ أم أن الشيفرتين السابقتين متساويتين؟ إن كان هنالك ميزّة اكتب مثلًا يوضحها.

الحل

عندما ننظر إلى الشيفرة الموجودة بداخل التابع يصبح الفرق جليًا.

يختلف السلوك إذا كان هناك "قفزة" من "try..catch".

على سبيل المثال، عندما يكون هناك تعليمة return بداخل صياغة try..catch. تعمل منغلقة finally في حالة وجود أي خروج من صياغة try..catch، حتى عبر تعليمة return: مباشرة بعد الانتهاء من try..catch ، ولكن قبل أن تحصل شيفرة الاستدعاء على التحكم.

function f() {
  try {
    alert('start');
    return "result";
  } catch (e) {
    /// ...
  } finally {
    alert('cleanup!');
  }
}

f(); // cleanup!

… أو عندما يكون هناك throw، مثل هنا:

function f() {
  try {
    alert('start');
    throw new Error("an error");
  } catch (e) {
    // ...
    if("can't handle the error") {
      throw e;
    }

  } finally {
    alert('cleanup!')
  }
}

f(); // cleanup!

تضمنُ finally عملية التنظيف هنا. إذا وضعنا الشيفرة البرمجية في نهاية f ، فلن تشغّل في هذا السيناريو.

ترجمة -وبتصرف- للفصل Error handling, "try..catch"‎ من كتاب The JavaScript language.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...