بغض النظر عن مدى روعتنا في البرمجة ، في بعض الأحيان تحتوي أكوادنا على أخطاء. قد تحدث بسبب أخطائنا ، إدخال المستخدم معلومة غير متوقعة ، واستجابة خاطئة للخادم ، ولآلاف الأسباب الأخرى.
عادة “يتعطل” البرنامج في حالة حدوث خطأ (يتوقف فورًا) ، مع ظهور الخطأ في وحدة التحكم.
لكن هناك بناء بناء جملة يسمى try...catch
يسمح لنا بـ “التقاط” الأخطاء حتى يمكن للنص أن يفعل شيئًا أكثر معقولية بدلاً من الانهيار.
بناء try…catch
يتكون بناء try…catch من مكونين رئيسيين: try
و catch
.
try {
// code...
} catch (err) {
// error handling
}
يعمل كالتالي:
- أولاً، يتم تنفيذ الشفرة داخل
try {...}
. - إذا لم تحدث أخطاء، يتم تجاهل
catch (err)
، ويتم استكمال تنفيذ الشفرة داخلtry
والانتقال إلى الخطوة التالية. - إذا حدث خطأ، فإن تنفيذ
try
يتوقف، ويتم نقل التحكم إلى بدايةcatch (err)
، وستحتوي المتغيرerr
(يمكن استخدام أي اسم آخر) على كائن خطأ يحتوي على تفاصيل حول ما حدث.
نعم، الخطأ داخل كتلة try {...}
لا يؤدي إلى إيقاف تشغيل البرنامج بل يتيح لنا فرصة التعامل معه في catch
.
دعونا نلقي نظرة على بعض الأمثلة.
-
مثال بدون أخطاء: يظهر
alert
(1)
وalert
(2)
:try { alert('بداية عمل try'); // (1) <-- // ...لا توجد أخطاء هنا alert('نهاية عمل try'); // (2) <-- } catch (err) { alert('بما أنه لا توجد أخطاء , تم تجاهل Catch'); // (3) }
-
مثال مع وجود خطأ: يظهر
alert
(1)
وalert
(3)
:try { alert('بداية عمل try'); // (1) <-- lalala; // خطأ ، لم يتم تعريف المتغير! alert('نهاية عمل try (لن يشتغل)'); // (2) } catch (err) { alert(`حدث خطأ !`); // (3) <-- }
try...catch
يعمل فقط لأخطاء وقت التشغيللن يعمل إذا كان الكود خاطئًا من الناحية النحوية ، على سبيل المثال يحتوي على أقواس و معقفات ناقصة:
try {
{{{{{{{{{{{{
} catch (err) {
alert("المحرك لا يستطيع فهم هذا الكود، فهو غير صالح");
}
يقرأ محرك JavaScript الكود أولاً ، ثم يشغلها. تسمى الأخطاء التي تحدث في مرحلة القراءة بأخطاء “وقت التحليل” ولا يمكن إصلاحها (من داخل هذا الكود). ذلك لأن المحرك لا يستطيع فهم الكود.
لذا، يمكن لـ try...catch
فقط التعامل مع الأخطاء التي تحدث في الشفرة الصحيحة. وتسمى هذه الأخطاء “أخطاء وقت التشغيل” أو في بعض الأحيان “الاستثناءات”.
try...catch
يعمل بشكل متزامنإذا حدث استثناء في الكود “المجدول”، مثل في setTimeout
، فلن يمكن لـ try...catch
التقاطه:
try {
setTimeout(function() {
noSuchVariable; // سيتوقف البرنامج هنا
}, 1000);
} catch (err) {
alert( "won't work" );
}
هذا لأن الدالة تنفذ في وقت لاحق، عندما يكون المحرك قد ترك بناء try...catch
.
للتقاط استثناء داخل دالة مجدولة، يجب أن يتم وضع try...catch
داخل تلك الدالة:
setTimeout(function() {
try {
noSuchVariable; // تتعامل الـ try...catch مع الخطأ!
} catch {
alert( "تم التقاط الخطأ هنا!" );
}
}, 1000);
الكائن الخطأ
عند حدوث خطأ ، يقوم JavaScript بإنشاء كائن يحتوي على تفاصيل خاصة به. ثم يتم تمرير الكائن كوسيطة في catch
:
try {
// ...
} catch (err) {
// <-- كائن الخطأ، يمكن استخدام كلمة أخرى بدلاً من err
// ...
}
بالنسبة لجميع الأخطاء المضمنة ، يحتوي هذا الكائن على خاصيتين رئيسيتين:
name
- اسم الخطأ. على سبيل المثال ، لمتغير غير محدد ، هذا
"ReferenceError"
. message
- رسالة نصية حول تفاصيل الخطأ.
هناك خصائص أخرى غير قياسية متاحة في معظم البيئات. واحدة من الأكثر استخدامًا ودعمًا هي:
stack
- مكدس الاستدعاء الحالي: سلسلة تحتوي على معلومات حول تسلسل الإستدعاءات المتداخلة التي أدت إلى الخطأ. تستخدم لأغراض التصحيح.
على سبيل المثال:
try {
lalala; // خطأ، المتغير غير معرف!
} catch (err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)
// يمكن أيضًا عرض الخطأ بشكل كامل
// يتم تحويل الخطأ إلى سلسلة نصية باسم "name: message"
alert(err); // ReferenceError: lalala is not defined
}
“catch” دون متغير
إذا لم نكن بحاجة إلى تفاصيل الخطأ, catch
يمكن أن تحثفه:
try {
// ...
} catch {
// <-- بدون (err)
// ...
}
استخدام “try…catch”
Let’s explore a real-life use case of try...catch
.
كما نعلم بالفعل, JavaScript تدعم JSON.parse(str) لقراءة قيمة JSON-encoded.
عادة يتم استخدامه لفك تشفير البيانات المستلمة عبر الشبكة ، من الخادم أو مصدر آخر.
نتلقى ذلك وندعو JSON.parse
مثل التالي:
let json = '{"name":"John", "age": 30}'; // البيانات من الخادم
let user = JSON.parse(json); // تحويل النص إلى كائن JS
// الآن هو كائن له خصائص من النص user
alert( user.name ); // John
alert( user.age ); // 30
يمكنك العثور على معلومات أكثر تفصيلاً حول JSON في فصل الكائن json وكيفية استخدامه.
إذا json
تالف, JSON.parse
يولد خطأ, و “يتوقف” البرنامج.
هل يجب أن نكون راضين عن ذلك؟ بالطبع لا!
بهذه الطريقة ، إذا كان هناك خطأ ما في البيانات ، فلن يعرف الزائر مطلقًا ذلك (ما لم يفتح وحدة تحكم مطور البرامج). والناس لا يحبون حقا عندما يتوقف شيء ما دون أي رسالة خطأ.
Let’s use try...catch
to handle the error:
let json = "{ bad json }";
try {
let user = JSON.parse(json); // <-- عندما يحدث خطأ...
alert( user.name ); // لا يعمل
} catch (err) {
// ...the execution jumps here
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
alert( err.name );
alert( err.message );
}
هنا نستخدم catch
فقط لإظهار الرسالة, كن يمكننا القيام بالمزيد: إرسال طلب شبكة جديد ، اقتراح بديل للزائر ، إرسال معلومات حول الخطأ إلى منشأة تسجيل ، …. كل شيء أفضل من توقف البرنامج.
صناعة الأخطاء الخاصة
ماذا إذا json
صحيح من الناحية اللغوية ، ولكن ليس له خاصية name
?
مثل هذا:
let json = '{ "age": 30 }'; // بيانات غير مكتملة
try {
let user = JSON.parse(json); // <-- لا أخطاء
alert( user.name ); // لا يوجد اسم !
} catch (err) {
alert( "doesn't execute" );
}
خنا يعمل JSON.parse
بطريقة عادية, لكن غياب name
في الواقع هو خطأ بالنسبة لنا.
التوحيد معالجة الأخطاء ، سنستخدم العامل throw
.
العامل “Throw”
العامل throw
يولد خطأ.
نقوم بكتابته كالتالي:
throw <error object>
من الناحية التقنية, يمكننا استخدام أي شيء ككائن خطأ. قد يكون حتى بدائيًا مثل رقم أو سلسلة ، ولكن من الأفضل أن يكون كائنًا ، ويفضل أن يكون مع خصائص name
و message
(للتوافق مع الأخطاء المضمنة).
يحتوي JavaScript على العديد من المنشئات المدمجة للأخطاء القياسية: Error
, SyntaxError
, ReferenceError
, TypeError
و اخرين. يمكننا استخدامها لإنشاء كائنات خطأ أيضًا.
نقوم بكتابتهم كالتالي:
let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...
للأخطاء المضمنة (ليس لأي كائنات ، للأخطاء فقط) ، خاصية name
هي بالضبط اسم المنشئ. وتأخذ خاصية message
من العوامل .
على سبيل المثال:
let error = new Error('حدث شيء ما o_O');
alert(error.name); // خطأ
alert(error.message); // حدث شيء ما o_O
دعونا نرى أي نوع من الأخطاء يولد JSON.parse
:
try {
JSON.parse("{ bad json o_O }");
} catch (err) {
alert(err.name); // SyntaxError
alert(err.message); // Unexpected token b in JSON at position 2
}
كما نرى ، هذا هو SyntaxError
.
وفي حالتنا غياب name
خطأ ، حيث يجب أن يكون لدى المستخدمين name
.
لذا دعونا نضف throw:
let json = '{ "age": 30 }'; // بيانات غير مكتملة
try {
let user = JSON.parse(json); // <-- لا اخطاء
if (!user.name) {
throw new SyntaxError("بيانات غير مكتملة: no name"); // (*)
}
alert( user.name );
} catch (err) {
alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name
}
في السطر (*)
, throw
يولد SyntaxError
مع المعطى message
, بنفس الطريقة التي تولدها JavaScript بنفسها.
عمل try
يتوقف على الفور و يتنقل إلى catch
.
الان catch
أصبح مكانًا واحدًا لكل معالجة الأخطاء: كلاهما لـ JSON.parse
وحالات أخرى.
إعادة رمي الإستثناءات
في المثال أعلاه، نستخدم try...catch
للتعامل مع البيانات غير الصحيحة. ولكن هل من الممكن أن يحدث “خطأ آخر غير متوقع” داخل كتلة try {...}
؟ مثل خطأ في البرمجة (متغير غير معرف) أو شيء آخر، وليس فقط هذا الأمر “البيانات غير الصحيحة”.
فمثلا:
let json = '{ "age": 30 }'; // بيانات غير مكتملة
try {
user = JSON.parse(json); // <-- user قبل "let" نسيت أن تضع
// ...
} catch (err) {
alert('JSON Error: ' + err); // JSON Error: ReferenceError: user is not defined
// (no JSON Error actually)
}
بالطبع ، كل شيء ممكن! المبرمجون يرتكبون الأخطاء. حتى في المرافق المفتوحة المصدر التي يستخدمها الملايين لعقود – فجأة يمكن اكتشاف خطأ يؤدي إلى اختراق رهيب.
في حالتنا، يتم وضع try...catch
للتقاط أخطاء “البيانات غير الصحيحة”. ولكن بحسب طبيعته، يلتقط catch
جميع الأخطاء من try
. هنا يتم التقاط خطأ غير متوقع، ولكن ما زالت الرسالة نفسها “خطأ JSON” تظهر، وهذا خاطئ ويجعل الكود أكثر صعوبة في التصحيح.
يجب أن يقوم Catch بمعالجة الأخطاء التي يعرفها و “إعادة رمي” كل الآخرين فقط.
يمكن شرح تقنية “إعادة الرمي” بمزيد من التفصيل على النحو التالي:
-
Catch يحصل على جميع الأخطاء.
-
في
catch(err) {...}
نقوم بتحليل كائن الخطأerr
. -
إذا لم نكن نعرف كيف نتعامل معها ، فنفعل
throw err
. -
يمسك
catch
بجميع الأخطاء. -
في كتلة
catch (err) {...}
، نحلل كائن الخطأerr
. -
إذا لم نعرف كيفية التعامل معه، فإننا نقوم بـ
throw err
.
عادةً، يمكننا التحقق من نوع الخطأ باستخدام عامل instanceof
.
try {
user = { /*...*/ };
} catch (err) {
if (err instanceof ReferenceError) {
alert('ReferenceError'); // "ReferenceError" للوصول إلى متغير غير محدد
}
}
يمكننا أيضًا الحصول على اسم فئة الخطأ من خاصية err.name
. جميع الأخطاء الأصلية لديها هذه الخاصية. خيار آخر هو قراءة err.constructor.name.
في الكود أدناه ، نستخدم إعادة رمي بحيث catch
يعالج فقط SyntaxError
:
let json = '{ "age": 30 }'; // بيانات غير مكتملة
try {
let user = JSON.parse(json);
if (!user.name) {
throw new SyntaxError("بيانات غير مكتملة: لا وجود لي name");
}
blabla(); // خطأ غير متوقع
alert( user.name );
} catch (err) {
if (err instanceof SyntaxError) {
alert( "JSON Error: " + err.message );
} else {
throw err; // rethrow (*)
}
}
إن رمي الخطأ في السطر (*)
من داخل كتلة catch
يسمح للخطأ بالخروج من try...catch
ويمكن التقاطه إما بواسطة هيكل try...catch
الخارجي (إذا وجد)، أو أنه يقتل النص البرمجي.
إذا catch
تعالج في الواقع الأخطاء التي تعرف كيف تتعامل معها و" تتخطى "جميع الآخرين.
يوضح المثال أدناه كيف يمكن التقاط مثل هذه الأخطاء بمستوى آخر من try...catch
:
function readData() {
let json = '{ "age": 30 }';
try {
// ...
blabla(); // خطأ!
} catch (err) {
// ...
if (!(err instanceof SyntaxError)) {
throw err; // rethrow (don't know how to deal with it)
}
}
}
try {
readData();
} catch (err) {
alert( "External catch got: " + err ); // caught it!
}
هنا، يعرف readData
كيفية التعامل مع SyntaxError
فقط، بينما يعرف try...catch
الخارجي كيفية التعامل مع كل شيء.
try…catch…finally
انتظر ، هذا ليس كل شيء.
The try...catch
construct may have one more code clause: finally
.
إذا كان موجودًا ، فإنه يعمل في جميع الحالات:
- بعد
try
, إذا لم تكن هناك أخطاء ، - بعد
catch
, إذا كانت هناك أخطاء.
تبدو الصيغة الموسعة كما يلي:
try {
... try to execute the code ...
} catch (err) {
... handle errors ...
} finally {
... يعمل في جميع الحالات ...
}
حاول تشغيل هذا الكود:
try {
alert('try');
if (confirm('Make an error?')) BAD_CODE();
} catch (err) {
alert('catch');
} finally {
alert('أخيرا');
}
يحتوي الكود على طريقتين للتنفيذ:
- إذا أجبت بـ “نعم” على “أخطأت؟”, ثم
try -> catch -> finally
. - إذا قلت “لا”, ثم
try -> finally
.
غالبًا ما يتم استخدام عبارة finally
عندما نبدأ في فعل شيء ونريد الانتهاء منه في أي حالة من النتائج.
على سبيل المثال ، نريد قياس الوقت الذي تستغرقه دالة أرقام فيبوناتشي fib(n)
. بطبيعة الحال ، يمكننا البدء في القياس قبل تشغيلها والانتهاء منها بعد ذلك. ولكن ماذا لو كان هناك خطأ أثناء استدعاء الدالة؟ على وجه الخصوص ، يؤدي تنفيذ fib(n)
في الكود أدناه إلى إرجاع خطأ للأرقام السالبة أو غير الصحيحة.
عبارة finally
هي مكان عظيم لإنهاء القياسات مهما كانت.
هنا finally
يضمن أن الوقت سيتم قياسه بشكل صحيح في كلتا الحالتين – في حالة تنفيذ fib
بنجاح وفي حالة حدوث خطأ فيه:
let num = +prompt("أدخل رقم صحيح موجب؟", 35)
let diff, result;
function fib(n) {
if (n < 0 || Math.trunc(n) != n) {
throw new Error("يجب ألا تكون سالبة ، وكذلك عددًا صحيحًا.");
}
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
let start = Date.now();
try {
result = fib(num);
} catch (err) {
result = 0;
} finally {
diff = Date.now() - start;
}
alert(result || "حدث خطأ");
alert( `${diff}ms مدة الإشتغال` );
يمكنك التحقق عن طريق تشغيل الكود بإدخال 35
في prompt
– يتم تنفيذه بشكل طبيعي, finally
بعد try
. ثم أدخل -1
– سيكون هناك خطأ فوري ، وسيستغرق التنفيذ 0ms
. تم إجراء القياسات بشكل صحيح.
بمعنى آخر ، قد تنتهي الدالة بـ return
أو throw
, وهذا لا يهم. تشتغل finally
في كلتا الحالتين.
try...catch...finally
وبخلاف ذلك ، إذا أعلنا عن let
في try
, فسوف تكون مرئية فقط داخلها.
finally
and return
The finally
clause works for any exit from try...catch
. That includes an explicit return
.
في المثال أدناه ، هناك return
في try
. في هذه الحالة ، تعمل finally
قبل العودة إلى الكود الخارجي مباشرةً.
function func() {
try {
return 1;
} catch (err) {
/* ... */
} finally {
alert( 'finally' );
}
}
alert( func() ); // يعمل التنبيه أولاً من finally ، ثم هذا
````smart header="`try...finally`"
بناء `try...finally`، بدون شرط `catch`، مفيد أيضًا. نستخدمه عندما لا نريد التعامل مع الأخطاء هنا (نتركها تسقط)، ولكن نريد التأكد من أن العمليات التي بدأناها قد انتهت.
```js
function func() {
// ابدأ في فعل شيء يحتاج إلى إكمال (مثل القياسات)
try {
// ...
} finally {
// أكمل هذا الشيء حتى لو توقف كل شيء
}
}
```
في الكود أعلاه ، هناك خطأ داخل `try` دائمًا ما يقع ، لأنه لا يوجد `catch`. ولكن `finally` يعمل قبل أن يترك التنفيذ الدالة.
catch شاملة
المعلومات الواردة في هذا القسم ليست جزءًا من أساسيات JavaScript.
لنفترض أن لدينا خطأ فادح خارج بناء try...catch
، وتعطل النص. مثل خطأ في البرمجة أو شيء مروع آخر.
هل هناك طريقة للرد على مثل هذه الحوادث؟ قد نرغب في تسجيل الخطأ وإظهار شيء للمستخدم (عادةً لا يرون رسائل خطأ) ، إلخ.
لا يوجد شيء في المواصفات ، ولكن البيئات توفرها عادةً ، لأنها مفيدة حقًا. على سبيل المثال, Node.js له process.on("uncaughtException")
for that. And in the من أجل هذا. وفي المتصفح يمكننا تعيين دالة للعامل الخاص 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(); // عفوًا ، حدث خطأ ما!
}
readData();
</script>
دور المعالج الشامل window.onerror
ادة لا يكون استرداد تنفيذ البرنامج النصي – ربما يكون ذلك مستحيلًا في حالة وجود أخطاء في البرمجة ، ولكن لإرسال رسالة الخطأ إلى المطورين.
هناك أيضًا خدمات الويب التي توفر تسجيل الأخطاء لمثل هذه الحالات ، مثل https://errorception.com أو http://www.muscula.com.
يعملون على هذا النحو:
- نسجل في الخدمة ونحصل على جزء JS (أو عنوان URL ) منها لإدراجها في الصفحات.
- يحدد هذا النص البرمجي لـ JS دالة
window.onerror
. - عند حدوث خطأ ، يرسل طلب شبكة حوله إلى الخدمة.
- يمكننا تسجيل الدخول إلى واجهة ويب الخدمة ونرى الأخطاء.
الخلاصة
تسمح هيكلة try...catch
بالتعامل مع أخطاء الوقت التشغيلي. وهي تسمح حرفيًا بـ “تجربة” تشغيل الكود و “التقاط” الأخطاء التي قد تحدث فيه.
الصيغة هي:
try {
// run this code
} catch (err) {
// if an error happened, then jump here
// err is the error object
} finally {
// تفعل في أي حال بعد try/catch
}
قد لا يكون هناك جزء catch
أو جزء finally
، لذلك فإن الهياكل القصيرة try...catch
و try...finally
صالحة أيضًا.
كائنات الخطأ لها الخصائص التالية:
message
– رسالة خطأ يمكن قراءتها و فهمها.name
– اسم الخطأ (اسم مُنشئ الخطأ).stack
(غير قياسي ، ولكنه مدعوم جيدًا) – المجموعة في لحظة إنشاء الخطأ.
If an error object is not needed, we can omit it by using catch {
instead of catch (err) {
.
يمكننا أيضًا إنشاء الأخطاء الخاصة بنا باستخدام عامل throw
. من الناحية التقنية ، يمكن أن يكون محتوى throw
متكون من أي شيء, ولكنها عادة ما تكون كائن خطأ يرث من فئة Error
. المزيد عن توسيع الأخطاء في الفصل التالي.
إعادة الرمي هو نمط مهم جدًا في معالجة الأخطاء: عادة ما تتوقع catch
وتعرف كيفية التعامل مع نوع الخطأ المعين, لذلك يجب أن تقوم بإعادة رمي الأخطاء التي لا تعرفها.
حتى إذا لم يكن لدينا try...catch
، فإن معظم البيئات تسمح لنا بإعداد معالج خطأ “عام” للقبض على الأخطاء التي تحدث بشكل خارجي. في المتصفح، هذا يعني استخدام window.onerror
.
التعليقات
<code>
، وللكثير من السطور استخدم<pre>
، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)