معالجة الأخطاء هي جانب أساسي من البرمجة يضمن أن التطبيقات يمكنها التعامل بشكل لائق مع المواقف غير المتوقعة. في JavaScript، على الرغم من أن try-catch
يُستخدم عادة، إلا أن هناك تقنيات متقدمة أكثر لتعزيز معالجة الأخطاء.
يستكشف هذا المقال هذه الطرق المتقدمة، ويقدم حلولًا عملية لتحسين استراتيجيات إدارة الأخطاء الخاصة بك وجعل تطبيقاتك أكثر متانة.
ما هي معالجة الأخطاء؟
غرض معالجة الأخطاء
معالجة الأخطاء تتوقع وتكتشف وتستجيب للمشاكل التي تحدث أثناء تنفيذ البرنامج. تحسن معالجة الأخطاء تجربة المستخدم، وتحافظ على استقرار التطبيق، وتضمن الموثوقية.
أنواع الأخطاء في JavaScript
- أخطاء الصياغة. هذه هي أخطاء في صيغة الكود، مثل نقص الأقواس أو استخدام الكلمات الرئيسية بشكل غير صحيح.
- أخطاء تشغيلية. تحدث أثناء التنفيذ، مثل الوصول إلى خصائص الكائنات غير المعرفة.
- أخطاء منطقية. هذه الأخطاء لا تؤدي إلى تعطل البرنامج ولكن تؤدي إلى نتائج غير صحيحة، غالبًا بسبب منطق معيب أو آثار جانبية غير مقصودة.
لماذا try-catch لا يكفي
قيود try-catch
- قيود النطاق. يتعامل فقط مع الكود المتزامن داخل كتلته ولا يؤثر على العمليات غير المتزامنة ما لم يتم التعامل معها بشكل خاص.
- الفشل الصامت. يمكن أن يؤدي الاستخدام المفرط أو السيء إلى تجاهل الأخطاء بصمت، مما قد يتسبب في سلوك غير متوقع.
- انتشار الأخطاء. لا يدعم بشكل طبيعي انتشار الأخطاء عبر طبقات مختلفة من التطبيق.
متى يتم استخدام try-catch
- رمز متزامن. فعال للتعامل مع الأخطاء في العمليات المتزامنة مثل تحليل JSON.
- الأقسام الحرجة. استخدم لحماية أقسام الكود الحرجة التي يمكن أن تكون لها عواقب وخيمة في حالة وجود أخطاء.
فئات الأخطاء المخصصة: تحسين معلومات الأخطاء
إنشاء فئة أخطاء مخصصة
تمديد فئات الأخطاء المضمنة Error
لتوفير معلومات إضافية:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.stack = (new Error()).stack; // Capture the stack trace
}
}
فوائد الأخطاء المخصصة
- وضوح. يوفر رسائل أخطاء محددة.
- التعامل التفصيلي. يسمح بالتعامل مع أنواع الأخطاء المحددة بشكل منفصل.
- بيانات الأخطاء. يتضمن سياقًا إضافيًا حول الخطأ.
حالات الاستخدام للأخطاء المخصصة
- فشل الصحة. أخطاء متعلقة بالتحقق من صحة إدخال المستخدم.
- أخطاء متخصصة لنطاق معين. أخطاء مصممة خصيصًا لنطاقات تطبيق معينة مثل المصادقة أو معالجة الدفع.
التعامل المركزي مع الأخطاء
التعامل المركزي مع الأخطاء في Node.js
تركيز التعامل مع الأخطاء باستخدام وسيط:
app.use((err, req, res, next) => {
console.error('Global error handler:', err);
res.status(500).json({ message: 'An error occurred' });
});
التعامل المركزي مع الأخطاء في تطبيقات الواجهة الأمامية
نفذ إدارة الأخطاء المركزية في React باستخدام حدود الأخطاء:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
مزايا إدارة الأخطاء المركزية
- الاتساق. يضمن نهجًا موحدًا لإدارة الأخطاء.
- سهولة الصيانة. تقليل مخاطر تفويت التغييرات بفضل التحديثات المركزية.
- سجلات ومراقبة أفضل. يُسهل التكامل مع أدوات المراقبة.
نقل الأخطاء
نقل الأخطاء في الشفرة متزامنة
استخدام throw
لنقل الأخطاء:
function processData(data) {
try {
validateData(data);
saveData(data);
} catch (error) {
console.error('Failed to process data:', error);
throw error;
}
}
نقل الأخطاء في الشفرة غير المتزامنة
تعامل مع الأخطاء باستخدام الوعود أو async
/await
:
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
متى نقل الأخطاء
- الأخطاء الحرجة. نقل الأخطاء التي تؤثر على التطبيق بأكمله.
- منطق الأعمال. السماح للمكونات على مستوى أعلى بمعالجة أخطاء منطق الأعمال.
معالجة الأخطاء في الشفرة غير المتزامنة
معالجة الأخطاء باستخدام async/await
استخدام try-catch
لإدارة الأخطاء في الدوال غير المتزامنة:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return await response.json();
} catch (error) {
console.error('Error fetching user data:', error);
return null;
}
}
استخدام Promise.all مع معالجة الأخطاء
معالجة عدة وعود وأخطاء:
async function fetchMultipleData(urls) {
try {
const responses = await Promise.all(urls.map
(url => fetch(url)));
return await Promise.all(responses.map(response => {
if (!response.ok) {
throw new Error(`Failed to fetch ${response.url}`);
}
return response.json();
}));
} catch (error) {
console.error('Error fetching multiple data:', error);
return [];
}
}
المشاكل الشائعة في معالجة الأخطاء في الشفرة غير المتزامنة
- الوعود الغير الملتقطة. تأكد دائمًا من التعامل مع الوعود باستخدام
await
،.then()
، أو.catch()
. - فشل صامت. تأكد من عدم ابتلاع الأخطاء بصمت.
- ظروف السباق. كن حذرًا مع العمليات المتزامنة الغير متزامنة.
تسجيل الأخطاء
تسجيل الأخطاء على الجانب العميل
التقاط الأخطاء العامة:
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error captured:', message, source, lineno, colno, error);
sendErrorToService({ message, source, lineno, colno, error });
};
تسجيل الأخطاء على الجانب الخادم
استخدام أدوات مثل Winston لتسجيل الأخطاء على الخادم
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'error.log' })]
});
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('An error occurred');
});
المراقبة والتنبيه
إعداد مراقبة وتنبيهات فورية باستخدام خدمات مثل PagerDuty أو Slack:
function notifyError(error) {
// Send error details to monitoring service
}
أفضل الممارسات في تسجيل الأخطاء
- تضمين السياق. سجل السياقات الإضافية مثل بيانات الطلب ومعلومات المستخدم.
- تجنب الإفراط في التسجيل. سجل المعلومات الأساسية لمنع مشاكل الأداء.
- تحليل السجلات بانتظام. استعرض السجلات بانتظام لاكتشاف المشاكل المتكررة والتعامل معها.
التدهور اللطيف والبدائل
التدهور اللطيف
قم بتصميم تطبيقك للاستمرار في العمل بإمكانيات مقلصة:
function renderProfilePicture(user) {
try {
if (!user.profilePicture) {
throw new Error('Profile picture not available');
}
return `<img data-fr-src="${user.profilePicture}" alt="Profile Picture">`;
} catch (error) {
console.error('Error rendering profile picture:', error.message);
return '<img src="/default-profile.png" alt="Default Profile Picture">';
}
}
آليات البديل
قدم بدائل عند فشل العمليات الأساسية:
async function fetchDataWithFallback(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
return { message: 'Default data' }; // Fallback data
}
}
تنفيذ التدهور اللطيف
- بدائل واجهة المستخدم. قدم عناصر واجهة مستخدم بديلة عند فشل الميزات.
- بدائل البيانات. استخدم القيم المخزنة مؤقتًا أو القيم الافتراضية عند عدم توفر البيانات الحية.
- آليات إعادة المحاولة. نفذ منطق إعادة المحاولة للأخطاء العابرة.
تحقيق التوازن في التدهور اللطيف
توازن بين توفير بدائل والإبقاء على المستخدمين على علم بالمشاكل:
function showErrorNotification(message) {
// Notify users about the issue
}
اختبار معالجة الأخطاء
اختبار وحدة معالجة الأخطاء
التحقق من معالجة الأخطاء في الوظائف الفردية:
const { validateUserInput } = require('./validation');
test('throws error for invalid username', () => {
expect(() => {
validateUserInput({ username: 'ab' });
}).toThrow('Username must be at least 3 characters long.');
});
اختبار التكامل
اختبار معالجة الأخطاء عبر طبقات التطبيق المختلفة:
test('fetches data with fallback on error', async () => {
fetch.mockReject(new Error('Network error'));
const data = await fetchDataWithFallback('https://api.example.com/data');
expect(data).toEqual({ message: 'Default data' });
});
اختبار نهاية إلى نهاية
محاكاة السيناريوهات الحقيقية لاختبار معالجة الأخطاء:
describe('ErrorBoundary', () => {
it('displays error message on error', () => {
cy.mount(<ErrorBoundary><MyComponent /></ErrorBoundary>);
cy.get(MyComponent).then(component => {
component.simulateError(new Error('Test error'));
});
cy.contains('Something went wrong.').should('be.visible');
});
});
أفضل الممارسات لاختبار معالجة الأخطاء
- تغطية الحالات الحدودية. تأكد من أن الاختبارات تغطي سيناريوهات الأخطاء المختلفة.
- اختبار البدائل. التحقق من عمل آليات البدائل كما هو مقصود.
- أتمتة الاختبار. استخدام أنابيب CI/CD لأتمتة وضمان معالجة الأخطاء القوية.
سيناريوهات حقيقية
السيناريو 1: نظام معالجة الدفعات
معالجة الأخطاء أثناء معالجة الدفعات:
- فئات الأخطاء المخصصة. استخدام فئات مثل
CardValidationError
،PaymentGatewayError
. - منطق إعادة المحاولة. تنفيذ عمليات إعادة المحاولة للمشكلات المتعلقة بالشبكة.
- تسجيل مركزي للأخطاء. مراقبة أخطاء الدفعات ومعالجتها بسرعة.
السيناريو 2: تطبيقات ذات بيانات كثيفة
إدارة الأخطاء في معالجة البيانات:
- التدهور الرشيق. توفير بيانات جزئية أو عروض بديلة.
- البيانات البديلة. استخدم القيم المخزنة أو الافتراضية.
- تسجيل الأخطاء. سجل السياق التفصيلي لإصلاح الأعطال.
سيناريو 3: مصادقة وتفويض المستخدم
تناول أخطاء المصادقة والتفويض:
- فئات الأخطاء المخصصة. أنشئ فئات مثل
AuthenticationError
,AuthorizationError
. - التعامل المركزي. سجل وراقب مشاكل المصادقة.
- التدهور اللطيف. قدم خيارات تسجيل الدخول البديلة ورسائل أخطاء ذات معنى.
الاستنتاج
يتطلب التعامل المتقدم مع الأخطاء في جافا سكريبت التجاوز عن بساطة try-catch
لاعتماد الأخطاء المخصصة والتعامل المركزي والنقل والاختبار القوي. تنفيذ هذه التقنيات يسمح لك ببناء تطبيقات قوية توفر تجربة مستخدم سلسة، حتى عند حدوث الأخطاء.
قراءة إضافية
- “JavaScript: The Good Parts” بقلم دوغلاس كروكفورد
- “You Don’t Know JS: Async & Performance” بقلم كايل سيمبسون
- MDN Web Docs: التعامل مع الأخطاء
Source:
https://dzone.com/articles/advanced-error-handling-in-javascript-custom-error