معالجة أفضل للأخطاء في NodeJS مع فئات الخطأ

نشرت: 2022-03-10
ملخص سريع ↬ هذه المقالة مخصصة لمطوري JavaScript و NodeJS الذين يرغبون في تحسين معالجة الأخطاء في تطبيقاتهم. يشرح Kelvin Omereshone نمط فئة error وكيفية استخدامه للحصول على طريقة أفضل وأكثر فاعلية لمعالجة الأخطاء عبر تطبيقاتك.

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

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

قبل المتابعة ، أود أن أشير إلى نوع الأخطاء التي سنناقشها في كيفية استخدام فئات الخطأ.

أخطاء تشغيلية

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

فيما يلي المزيد من الأمثلة على الأخطاء التشغيلية:

  • فشل الاتصال بخادم قاعدة البيانات ؛
  • المدخلات غير الصالحة من قبل المستخدم (يستجيب الخادم برمز استجابة 400 ) ؛
  • طلب مهلة ؛
  • لم يتم العثور على المورد (الخادم يستجيب برمز استجابة 404) ؛
  • يعود الخادم مع استجابة 500 .

من الجدير بالملاحظة أيضًا مناقشة نظير الأخطاء التشغيلية بإيجاز.

أخطاء المبرمج

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

  • محاولة قراءة خاصية على كائن لم يتم تعريفه.
 const user = { firstName: 'Kelvin', lastName: 'Omereshone', } console.log(user.fullName) // throws 'undefined' because the property fullName is not defined
  • استدعاء أو استدعاء وظيفة غير متزامنة دون رد اتصال.
  • تمرير سلسلة حيث كان من المتوقع وجود رقم.

تتناول هذه المقالة معالجة الأخطاء التشغيلية في NodeJS. يختلف التعامل مع الأخطاء في NodeJS بشكل كبير عن معالجة الأخطاء في اللغات الأخرى. هذا بسبب الطبيعة غير المتزامنة لجافا سكريبت وانفتاح جافا سكريبت مع الأخطاء. دعني أوضح:

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

على سبيل المثال ، قد يقرر مطور JavaScript طرح رقم بدلاً من مثيل كائن خطأ ، مثل:

 // bad throw 'Whoops :)'; // good throw new Error('Whoops :)')

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

دعونا نلقي نظرة على بعض الأنماط غير الصحيحة في معالجة الأخطاء ، قبل إلقاء نظرة على نمط فئة الخطأ وكيف أنها طريقة أفضل بكثير لمعالجة الأخطاء في NodeJS.

المزيد بعد القفز! أكمل القراءة أدناه ↓

نمط معالجة الأخطاء السيئة رقم 1: الاستخدام الخاطئ لعمليات الاسترجاعات

سيناريو العالم الحقيقي : تعتمد التعليمات البرمجية الخاصة بك على واجهة برمجة تطبيقات خارجية تتطلب رد اتصال للحصول على النتيجة التي تتوقع أن تعود.

لنأخذ مقتطف الشفرة أدناه:

 'use strict'; const fs = require('fs'); const write = function () { fs.mkdir('./writeFolder'); fs.writeFile('./writeFolder/foobar.txt', 'Hello World'); } write();

حتى NodeJS 8 وما فوق ، كان الكود أعلاه شرعيًا ، وكان المطورون ببساطة يطلقون الأوامر وينسونها. هذا يعني أن المطورين لم يكونوا مطالبين بتوفير رد اتصال لمثل هذه الاستدعاءات للوظائف ، وبالتالي يمكن أن يتركوا معالجة الأخطاء. ماذا يحدث عندما لا يتم إنشاء writeFolder ؟ لن يتم إجراء استدعاء writeFile ولن نعرف أي شيء عنه. قد يؤدي هذا أيضًا إلى حالة السباق لأن الأمر الأول ربما لم ينته عند بدء الأمر الثاني مرة أخرى ، فلن تعرف.

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

 'use strict'; const fs = require('fs'); const write = function () { fs.mkdir('./writeFolder', () => { fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); }); } write();

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

خطأ في التعامل مع عمليات الاسترجاعات

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

 'use strict'; // Wrong const fs = require('fs'); const write = function (callback) { fs.mkdir('./writeFolder', (err, data) => { if (data) fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); else callback(err) }); } write(console.log);

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

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

 'use strict'; // Right const fs = require('fs'); const write = function (callback) { fs.mkdir('./writeFolder', (err, data) => { if (err) return callback(err) fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); }); } write(console.log);

نموذج معالجة الخطأ رقم 2: الاستخدام الخاطئ للوعود

سيناريو العالم الحقيقي : لقد اكتشفت الوعود وتعتقد أنها أفضل بكثير من عمليات الاسترجاعات بسبب جحيم رد الاتصال وقررت التعهد ببعض واجهة برمجة التطبيقات الخارجية التي تعتمد عليها قاعدة الشفرة الخاصة بك. أو أنك تستهلك وعدًا من واجهة برمجة تطبيقات خارجية أو واجهة برمجة تطبيقات للمتصفح مثل وظيفة fetch ().

في هذه الأيام ، لا نستخدم عمليات الاسترجاعات في قواعد البرمجة NodeJS الخاصة بنا ، بل نستخدم الوعود. لذلك دعونا نعيد تنفيذ كود المثال الخاص بنا مع وعد:

 'use strict'; const fs = require('fs').promises; const write = function () { return fs.mkdir('./writeFolder').then(() => { fs.writeFile('./writeFolder/foobar.txt', 'Hello world!') }).catch((err) => { // catch all potential errors console.error(err) }) }

دعنا نضع الكود أعلاه تحت المجهر - يمكننا أن نرى أننا نتفرع من وعد fs.mkdir إلى سلسلة وعد أخرى (استدعاء fs.writeFile) دون حتى التعامل مع استدعاء الوعد هذا. قد تعتقد أن أفضل طريقة للقيام بذلك هي:

 'use strict'; const fs = require('fs').promises; const write = function () { return fs.mkdir('./writeFolder').then(() => { fs.writeFile('./writeFolder/foobar.txt', 'Hello world!').then(() => { // do something }).catch((err) => { console.error(err); }) }).catch((err) => { // catch all potential errors console.error(err) }) }

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

التعهد بواجهة برمجة تطبيقات تعتمد على رد الاتصال

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

 function doesWillNotAlwaysSettle(arg) { return new Promise((resolve, reject) => { doATask(foo, (err) => { if (err) { return reject(err); } if (arg === true) { resolve('I am Done') } }); }); }

مما سبق ، إذا كانت arg غير true ولم يكن لدينا خطأ من الاستدعاء لوظيفة doATask ، فسيتم تعليق هذا الوعد وهو تسرب للذاكرة في تطبيقك.

ابتلع أخطاء المزامنة في الوعود

استخدام مُنشئ الوعد لديه العديد من الصعوبات ، واحدة من هذه الصعوبات هي ؛ حالما يتم حلها أو رفضها لا يمكن أن تحصل على دولة أخرى. هذا لأن الوعد لا يمكنه الحصول إلا على حالة واحدة - إما أنه معلق أو تم حله / رفضه. هذا يعني أنه يمكن أن يكون لدينا مناطق ميتة في وعودنا. دعنا نرى هذا في الكود:

 function deadZonePromise(arg) { return new Promise((resolve, reject) => { doATask(foo, (err) => { resolve('I'm all Done'); throw new Error('I am never reached') // Dead Zone }); }); }

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

أمثلة من العالم الحقيقي

تساعد الأمثلة أعلاه في شرح أنماط معالجة الأخطاء السيئة ، فلنلقِ نظرة على نوع المشكلات التي قد تراها في الحياة الواقعية.

مثال العالم الحقيقي رقم 1 - تحويل الخطأ إلى سلسلة

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

 'use strict'; function readTemplate() { return new Promise(() => { databaseGet('query', function(err, data) { if (err) { reject('Template not found. Error: ', + err); } else { resolve(data); } }); }); } readTemplate();

دعنا ننظر إلى الخطأ في الكود أعلاه. مما سبق ، نرى أن المطور يحاول تحسين الخطأ الناتج عن databaseGet Get API من خلال ربط الخطأ الذي تم إرجاعه بالسلسلة "لم يتم العثور على القالب". يحتوي هذا الأسلوب على الكثير من الجوانب السلبية لأنه عند إجراء التسلسل ، يقوم المطور ضمنيًا بتشغيل toString على كائن الخطأ الذي تم إرجاعه. بهذه الطريقة يفقد أي معلومات إضافية ناتجة عن الخطأ (قل وداعًا لتكديس التتبع). إذن ما يمتلكه المطور الآن هو مجرد سلسلة غير مفيدة عند تصحيح الأخطاء.

أفضل طريقة هي الاحتفاظ بالخطأ كما هو أو لفه بخطأ آخر قمت بإنشائه وإرفاق الخطأ الذي تم إلقاؤه من قاعدة البيانات Get call كخاصية له.

مثال من العالم الواقعي # 2: تجاهل الخطأ تمامًا

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

 router.get('/:id', function (req, res, next) { database.getData(req.params.userId) .then(function (data) { if (data.length) { res.status(200).json(data); } else { res.status(404).end(); } }) .catch(() => { log.error('db.rest/get: could not get data: ', req.params.userId); res.status(500).json({error: 'Internal server error'}); }) });

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

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

مثال العالم الحقيقي # 3: عدم قبول الخطأ الذي تم إلقاؤه من واجهة برمجة التطبيقات

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

خذ المثال التالي من الكود أدناه:

 async function doThings(input) { try { validate(input); try { await db.create(input); } catch (error) { error.message = `Inner error: ${error.message}` if (error instanceof Klass) { error.isKlass = true; } throw error } } catch (error) { error.message = `Could not do things: ${error.message}`; await rollback(input); throw error; } }

يحدث الكثير في الكود أعلاه قد يؤدي إلى تصحيح أخطاء الرعب. لنلقي نظرة:

  • تغليف كتل try/catch : يمكنك أن ترى مما سبق أننا نقوم بلف كتلة try/catch وهي فكرة سيئة للغاية. نحاول عادةً تقليل استخدام كتل try/catch لتقليل السطح حيث يتعين علينا معالجة خطأنا (فكر في الأمر على أنه معالجة خطأ DRY) ؛
  • نحن نتلاعب أيضًا برسالة الخطأ في محاولة للتحسين وهي أيضًا ليست فكرة جيدة ؛
  • نحن نتحقق مما إذا كان الخطأ هو مثيل من النوع Klass وفي هذه الحالة ، نقوم بتعيين خاصية منطقية للخطأ isKlass إلى truev (ولكن إذا نجح هذا التحقق ، فسيكون الخطأ من النوع Klass ) ؛
  • نحن أيضًا بصدد استعادة قاعدة البيانات في وقت مبكر جدًا لأنه ، من هيكل الكود ، هناك ميل كبير إلى أننا ربما لم نصل حتى إلى قاعدة البيانات عندما تم إلقاء الخطأ.

فيما يلي طريقة أفضل لكتابة الكود أعلاه:

 async function doThings(input) { validate(input); try { await db.create(input); } catch (error) { try { await rollback(); } catch (error) { logger.log('Rollback failed', error, 'input:', input); } throw error; } }

دعنا نحلل ما نقوم به بشكل صحيح في المقتطف أعلاه:

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

اختبارات

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

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

مثال العالم الحقيقي رقم 4: الرفض غير المعالج

إذا كنت قد استخدمت الوعود من قبل ، فمن المحتمل أنك واجهت unhandled rejections .

فيما يلي كتاب تمهيدي سريع عن حالات الرفض غير المعالجة. الرفض الذي لم يتم التعامل معه هو رفض للوعد لم يتم التعامل معه. هذا يعني أنه تم رفض الوعد ولكن الرمز الخاص بك سيستمر.

لنلقِ نظرة على مثال واقعي شائع يؤدي إلى رفض غير معالج ..

 'use strict'; async function foobar() { throw new Error('foobar'); } async function baz() { throw new Error('baz') } (async function doThings() { const a = foobar(); const b = baz(); try { await a; await b; } catch (error) { // ignore all errors! } })();

قد يبدو أن الكود أعلاه للوهلة الأولى ليس عرضة للخطأ. لكن عند إلقاء نظرة فاحصة ، بدأنا في رؤية عيب. دعني أوضح: ماذا يحدث عندما يتم رفض a ؟ هذا يعني أن await b لا يتم الوصول إليه أبدًا وهذا يعني رفضه غير المعالج. أحد الحلول الممكنة هو استخدام Promise.all في كلا الوعدين. لذلك سيقرأ الكود كما يلي:

 'use strict'; async function foobar() { throw new Error('foobar'); } async function baz() { throw new Error('baz') } (async function doThings() { const a = foobar(); const b = baz(); try { await Promise.all([a, b]); } catch (error) { // ignore all errors! } })();

في ما يلي سيناريو آخر في العالم الواقعي قد يؤدي إلى خطأ في رفض الوعد غير المعالج:

 'use strict'; async function foobar() { throw new Error('foobar'); } async function doThings() { try { return foobar() } catch { // ignoring errors again ! } } doThings();

إذا قمت بتشغيل مقتطف الشفرة أعلاه ، فستتلقى وعدًا مرفوضًا ، وإليكم السبب: على الرغم من أنه ليس واضحًا ، فإننا نعيد الوعد (foobar) قبل أن نتعامل معه مع try/catch . ما يجب أن نفعله هو انتظار الوعد الذي نتعامل معه مع try/catch حتى يقرأ الكود:

 'use strict'; async function foobar() { throw new Error('foobar'); } async function doThings() { try { return await foobar() } catch { // ignoring errors again ! } } doThings();

التلخيص في الأمور السلبية

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

فئات الخطأ

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

  • ApplicationError
    هذا هو أصل جميع فئات الخطأ الأخرى ، أي أن جميع فئات الخطأ الأخرى ترث منه.
  • DatabaseError
    أي خطأ يتعلق بعمليات قاعدة البيانات سيرث من هذه الفئة.
  • UserFacingError
    سيتم توريث أي خطأ ناتج عن تفاعل المستخدم مع التطبيق من هذه الفئة.

إليك كيف سيبدو ملف فئة error الخاص بنا:

 'use strict'; // Here is the base error classes to extend from class ApplicationError extends Error { get name() { return this.constructor.name; } } class DatabaseError extends ApplicationError { } class UserFacingError extends ApplicationError { } module.exports = { ApplicationError, DatabaseError, UserFacingError }

يمكّننا هذا النهج من التمييز بين الأخطاء التي يلقيها تطبيقنا. لذا إذا أردنا الآن معالجة خطأ طلب سيئ (إدخال مستخدم غير صالح) أو خطأ غير موجود (لم يتم العثور على المورد) يمكننا أن نرث من الفئة الأساسية وهي UserFacingError (كما في الكود أدناه).

 const { UserFacingError } = require('./baseErrors') class BadRequestError extends UserFacingError { constructor(message, options = {}) { super(message); // You can attach relevant information to the error instance // (eg. the username) for (const [key, value] of Object.entries(options)) { this[key] = value; } } get statusCode() { return 400; } } class NotFoundError extends UserFacingError { constructor(message, options = {}) { super(message); // You can attach relevant information to the error instance // (eg. the username) for (const [key, value] of Object.entries(options)) { this[key] = value; } } get statusCode() { return 404 } } module.exports = { BadRequestError, NotFoundError }

تتمثل إحدى مزايا نهج فئة error في أنه إذا طرحنا أحد هذه الأخطاء ، على سبيل المثال ، NotFoundError ، فسيكون كل مطور يقرأ قاعدة التعليمات البرمجية هذا قادرًا على فهم ما يجري في هذه المرحلة الزمنية (إذا قرأوا الكود) ).

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

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

تلميحات حول استخدام فئات الخطأ

  • قم بإنشاء الوحدة النمطية الخاصة بك (ربما وحدة خاصة) لكل فئة خطأ بهذه الطريقة يمكنك ببساطة استيرادها في التطبيق الخاص بك واستخدامها في كل مكان.
  • قم برمي الأخطاء التي تهتم بها فقط (الأخطاء التي تمثل حالات لفئات الخطأ الخاصة بك). بهذه الطريقة تعرف أن فئات الخطأ الخاصة بك هي مصدر الحقيقة الوحيد الخاص بك وتحتوي على جميع المعلومات اللازمة لتصحيح أخطاء التطبيق الخاص بك.
  • يعد وجود وحدة خطأ مجردة مفيدًا جدًا لأننا نعرف الآن أن جميع المعلومات الضرورية المتعلقة بالأخطاء التي يمكن أن تتسبب فيها تطبيقاتنا في مكان واحد.
  • معالجة الأخطاء في الطبقات. إذا كنت تتعامل مع الأخطاء في كل مكان ، فلديك نهج غير متسق في معالجة الأخطاء يصعب تتبعه. أعني بالطبقات مثل قاعدة البيانات ، وطبقات Express / fastify / HTTP ، وما إلى ذلك.

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

 const { DatabaseError } = require('./error') const { NotFoundError } = require('./userFacingErrors') const { UserFacingError } = require('./error') // Express app.get('/:id', async function (req, res, next) { let data try { data = await database.getData(req.params.userId) } catch (err) { return next(err); } if (!data.length) { return next(new NotFoundError('Dataset not found')); } res.status(200).json(data) }) app.use(function (err, req, res, next) { if (err instanceof UserFacingError) { res.sendStatus(err.statusCode); // or res.status(err.statusCode).send(err.errorCode) } else { res.sendStatus(500) } // do your logic logger.error(err, 'Parameters: ', req.params, 'User data: ', req.user) });

مما سبق ، فإننا نستفيد من أن Express يكشف عن معالج أخطاء عالمي يسمح لك بمعالجة جميع أخطائك في مكان واحد. يمكنك رؤية الاستدعاء next() في الأماكن التي نتعامل فيها مع الأخطاء. سيؤدي هذا الاستدعاء إلى تمرير الأخطاء إلى المعالج المحدد في قسم app.use . لأن Express لا يدعم غير المتزامن / انتظار ، فإننا نستخدم كتل try/catch .

لذا من الكود أعلاه ، للتعامل مع أخطائنا ، نحتاج فقط إلى التحقق مما إذا كان الخطأ الذي تم إلقاؤه هو مثيل UserFacingError تلقائيًا أنه سيكون هناك رمز حالة في كائن الخطأ ونرسله إلى المستخدم (قد ترغب في ذلك) أن يكون لديك رمز خطأ محدد أيضًا يمكنك تمريره إلى العميل) وهذا هو إلى حد كبير.

ستلاحظ أيضًا أنه في هذا النمط (نمط فئة error ) كل خطأ آخر لم ترميه بشكل صريح هو خطأ 500 لأنه شيء غير متوقع يعني أنك لم ترمي هذا الخطأ صراحة في التطبيق الخاص بك. بهذه الطريقة ، يمكننا التمييز بين أنواع الأخطاء التي تحدث في تطبيقاتنا.

خاتمة

يمكن أن تجعل المعالجة الصحيحة للأخطاء في تطبيقك تنام بشكل أفضل في الليل وتوفر وقت التصحيح. فيما يلي بعض النقاط الرئيسية الجاهزة للاستفادة من هذه المقالة:

  • استخدام فئات الأخطاء التي تم إعدادها خصيصًا لتطبيقك ؛
  • تنفيذ معالجات الأخطاء المجردة ؛
  • استخدم دائمًا غير متزامن / انتظار ؛
  • جعل الأخطاء معبرة ؛
  • يعد المستخدم إذا لزم الأمر ؛
  • إعادة حالات الخطأ والأكواد المناسبة ؛
  • استفد من خطافات الوعد.

The Smashing Cat تستكشف رؤى جديدة ، في Smashing Workshops بالطبع.

واجهة أمامية مفيدة وبتات UX مفيدة ، يتم تسليمها مرة واحدة في الأسبوع.

مع الأدوات التي تساعدك على إنجاز عملك بشكل أفضل. اشترك واحصل على قوائم التحقق من تصميم الواجهة الذكية من Vitaly بتنسيق PDF عبر البريد الإلكتروني.

على الواجهة الأمامية وتجربة المستخدم. يثق به 190.000 شخص.