تعتبر سلاسل الوعد رائعة في معالجة الأخطاء. عند رفض الوعد، ينتقل عنصر التحكم إلى أقرب معالج رفض. هذا مريح للغاية في الممارسة العملية.
على سبيل المثال، في الكود الموجود أسفل عنوان URL المراد fetch
، هناك خطأ (لا يوجد مثل هذا الموقع) ويعالج .catch
الخطأ:
جلب ('https://no- such-server.blabla') // يرفض .ثم(الاستجابة => الاستجابة.json()) .catch(err => تنبيه(err)) // TypeError: فشل في الجلب (قد يختلف النص)
كما ترون، ليس من الضروري أن يكون .catch
فوريًا. وقد يظهر بعد واحد أو ربما عدة .then
.
أو ربما يكون كل شيء على ما يرام في الموقع، لكن الاستجابة غير صالحة بتنسيق JSON. أسهل طريقة لاكتشاف كافة الأخطاء هي إلحاق .catch
بنهاية السلسلة:
جلب('https://javascript.info/article/promise-chaining/user.json') .ثم(الاستجابة => الاستجابة.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .ثم(الاستجابة => الاستجابة.json()) .then(githubUser => وعد جديد((حل، رفض) => { Let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "مثال للصورة الرمزية للوعد"; document.body.append(img); setTimeout(() => { img.remove(); حل(githubUser); }, 3000); })) .catch(error => تنبيه(error.message));
عادةً، لا يتم تشغيل مثل هذا .catch
على الإطلاق. ولكن إذا تم رفض أي من الوعود المذكورة أعلاه (مشكلة في الشبكة أو json غير صالح أو أي شيء آخر)، فسيتم اكتشافه.
تحتوي التعليمات البرمجية الخاصة بمنفذ الوعد ومعالجات الوعد على " try..catch
غير مرئية.. التقاط" حولها. إذا حدث استثناء، فسيتم اكتشافه ومعاملته على أنه رفض.
على سبيل المثال هذا الكود:
وعد جديد((حل، رفض) => { رمي خطأ جديد ("عفوًا!")؛ }).catch(alert); // خطأ: عفوًا!
...يعمل تمامًا كما يلي:
وعد جديد((حل، رفض) => { رفض(خطأ جديد("عفوا!")); }).catch(alert); // خطأ: عفوًا!
" try..catch
غير المرئية" المحيطة بالمنفذ تكتشف الخطأ تلقائيًا وتحوله إلى وعد مرفوض.
لا يحدث هذا في وظيفة المنفذ فحسب، بل في معالجاته أيضًا. إذا throw
داخل معالج .then
، فهذا يعني رفض الوعد، لذلك ينتقل عنصر التحكم إلى أقرب معالج خطأ.
هنا مثال:
وعد جديد((حل، رفض) => { حل("موافق"); }).ثم((النتيجة) => { رمي خطأ جديد ("عفوًا!")؛ // يرفض الوعد }).catch(alert); // خطأ: عفوًا!
يحدث هذا لجميع الأخطاء، وليس فقط تلك الناتجة عن عبارة throw
. على سبيل المثال خطأ برمجي:
وعد جديد((حل، رفض) => { حل("موافق"); }).ثم((النتيجة) => { بلابلا(); // لا توجد مثل هذه الوظيفة }).catch(alert); // خطأ مرجعي: لم يتم تعريف blabla
لا يلتقط .catch
الأخير حالات الرفض الصريحة فحسب، بل يكتشف أيضًا الأخطاء العرضية في المعالجات أعلاه.
كما لاحظنا سابقًا، فإن .catch
في نهاية السلسلة مشابه لـ try..catch
. قد يكون لدينا العدد الذي نريده من معالجات .then
، ثم نستخدم .catch
واحدًا في النهاية لمعالجة الأخطاء الموجودة فيها جميعًا.
في try..catch
عادية..catch يمكننا تحليل الخطأ وربما إعادة طرحه إذا لم يكن من الممكن معالجته. نفس الشيء ممكن بالنسبة للوعود.
إذا throw
داخل .catch
، فسينتقل عنصر التحكم إلى أقرب معالج خطأ تالي. وإذا تعاملنا مع الخطأ وانتهينا بشكل طبيعي، فإنه يستمر إلى المعالج التالي .then
الناجح.
في المثال أدناه، يعالج .catch
الخطأ بنجاح:
// التنفيذ: قبض -> ثم وعد جديد((حل، رفض) => { رمي خطأ جديد ("عفوًا!")؛ }).catch(وظيفة(خطأ) { تنبيه ("تمت معالجة الخطأ، تابع بشكل طبيعي")؛ }).then(() => تنبيه("تشغيل المعالج الناجح التالي"));
هنا تنتهي كتلة .catch
بشكل طبيعي. لذلك يتم استدعاء المعالج .then
التالي.
في المثال أدناه نرى الوضع الآخر مع .catch
. اكتشف المعالج (*)
الخطأ ولا يمكنه التعامل معه (على سبيل المثال، يعرف فقط كيفية التعامل مع URIError
)، لذلك يرميه مرة أخرى:
// التنفيذ: قبض -> قبض وعد جديد((حل، رفض) => { رمي خطأ جديد ("عفوًا!")؛ }).catch(وظيفة(خطأ) {// (*) إذا (خطأ في مثيل URIError) { // التعامل معها } آخر { تنبيه ("لا يمكن التعامل مع مثل هذا الخطأ")؛ خطأ في الرمي // يؤدي رمي هذا الخطأ أو خطأ آخر إلى الانتقال إلى الصيد التالي } }).ثم (وظيفة () { /* لا يعمل هنا */ }).catch(خطأ => { // (**) تنبيه("حدث خطأ غير معروف: ${خطأ}`); // لا تُرجع أي شيء => التنفيذ يسير بالطريقة العادية });
يقفز التنفيذ من أول .catch
(*)
إلى التالي (**)
أسفل السلسلة.
ماذا يحدث عندما لا تتم معالجة الخطأ؟ على سبيل المثال، نسينا إضافة .catch
إلى نهاية السلسلة، كما هو الحال هنا:
وعد جديد (وظيفة () { noSuchFunction(); // خطأ هنا (لا توجد مثل هذه الوظيفة) }) .ثم(() => { // معالجات الوعد الناجحة، واحدة أو أكثر }); // بدون .catch في النهاية!
في حالة حدوث خطأ، يصبح الوعد مرفوضًا، ويجب أن ينتقل التنفيذ إلى أقرب معالج رفض. ولكن لا يوجد شيء. لذا فإن الخطأ "عالق". لا يوجد رمز للتعامل معها.
من الناحية العملية، تمامًا كما هو الحال مع الأخطاء العادية التي لم تتم معالجتها في التعليمات البرمجية، فهذا يعني أن شيئًا ما قد حدث خطأً فادحًا.
ماذا يحدث عندما يحدث خطأ عادي ولا يتم اكتشافه عن طريق try..catch
؟ يموت البرنامج النصي برسالة في وحدة التحكم. يحدث شيء مماثل مع رفض الوعد الذي لم تتم معالجته.
يتتبع محرك JavaScript حالات الرفض هذه وينشئ خطأً عامًا في هذه الحالة. يمكنك رؤيته في وحدة التحكم إذا قمت بتشغيل المثال أعلاه.
يمكننا اكتشاف مثل هذه الأخطاء في المتصفح باستخدام الحدث unhandledrejection
:
window.addEventListener('unhandledrejection', function(event) { // كائن الحدث له خاصيتان خاصتان: تنبيه(event.promise); // [وعد الكائن] - الوعد الذي أدى إلى حدوث الخطأ تنبيه(event.reason); // خطأ: عفوًا! - كائن الخطأ غير المعالج }); وعد جديد (وظيفة () { رمي خطأ جديد ("عفوًا!")؛ }); // لا يوجد صيد للتعامل مع الخطأ
الحدث هو جزء من معيار HTML.
إذا حدث خطأ، ولم يكن هناك .catch
، فسيتم تشغيل معالج unhandledrejection
، ويحصل على كائن event
مع المعلومات حول الخطأ، حتى نتمكن من القيام بشيء ما.
عادةً ما تكون هذه الأخطاء غير قابلة للاسترداد، لذا فإن أفضل طريقة لدينا هي إعلام المستخدم بالمشكلة وربما الإبلاغ عن الحادث إلى الخادم.
في البيئات غير المستعرضة مثل Node.js، هناك طرق أخرى لتتبع الأخطاء التي لم تتم معالجتها.
يعالج .catch
الأخطاء في جميع أنواع الوعود: سواء كان ذلك استدعاء reject()
، أو خطأ تم طرحه في المعالج.
.then
يقوم أيضًا باكتشاف الأخطاء بنفس الطريقة، إذا تم إعطاء الوسيطة الثانية (وهو معالج الأخطاء).
يجب أن نضع .catch
بالضبط في الأماكن التي نريد معالجة الأخطاء فيها ومعرفة كيفية التعامل معها. يجب على المعالج تحليل الأخطاء (تساعد فئات الأخطاء المخصصة) وإعادة طرح الأخطاء غير المعروفة (ربما تكون أخطاء في البرمجة).
لا بأس بعدم استخدام .catch
على الإطلاق، إذا لم تكن هناك طريقة للتعافي من الخطأ.
على أي حال، يجب أن يكون لدينا معالج الأحداث unhandledrejection
(للمتصفحات، والمثيلات للبيئات الأخرى) لتتبع الأخطاء التي لم تتم معالجتها وإبلاغ المستخدم (وربما خادمنا) عنها، حتى لا "يموت" تطبيقنا أبدًا.
ماذا تعتقد؟ هل سيتم تشغيل .catch
؟ اشرح إجابتك.
وعد جديد (وظيفة (حل، رفض) { setTimeout(() => { رمي خطأ جديد ("عفوًا!")؛ }, 1000); }).catch(alert);
الجواب هو: لا، لن :
وعد جديد (وظيفة (حل، رفض) { setTimeout(() => { رمي خطأ جديد ("عفوًا!")؛ }, 1000); }).catch(alert);
كما ذكرنا في هذا الفصل، هناك " try..catch
ضمنية.. التقاط" حول رمز الوظيفة. لذلك تتم معالجة كافة الأخطاء المتزامنة.
ولكن هنا لا يتم إنشاء الخطأ أثناء تشغيل المنفذ، ولكن لاحقًا. لذلك الوعد لا يمكن التعامل معه.