DanLevy.net

اختبار: إتقان أخطاء جافا سكريبت المتقدمة

هل استثناءاتك استثنائية حقًا؟

هل تعتقد أنك تعرف أخطاء JavaScript من الداخل والخارج؟

ماذا يُرجع JSON.stringify(error)؟

const error = new Error('Oops');
console.log(JSON.stringify(error));

فكر في الخصائص القابلة للتعداد في كائنات الخطأ.

كائنات الخطأ تحتوي على خصائص غير قابلة للتعداد (message, name, stack)، لذا فإن JSON.stringify() يُرجع {}. هذا خطأ شائع عند إرسال الأخطاء في استجابات API. استخدم JSON.stringify(error, Object.getOwnPropertyNames(error)) أو أنشئ كائنًا عاديًا بدلاً من ذلك.

ما الفرق بين هذين؟

const err = new Error('Test');
console.log(err);
console.log(JSON.stringify(err));

فكر في كيفية تعامل console.log مع الكائنات مقابل تسلسل JSON.

يظهر console.log(err) الخطأ مع رسالته وتتبع المكدس لأن وحدة التحكم لديها معالجة خاصة لكائنات الخطأ. بينما يُرجع JSON.stringify(err) '{}' لأن خصائص الخطأ غير قابلة للتعداد. هذا الاختلاف يوقع العديد من المطورين في حيرة أثناء تصحيح واجهات برمجة التطبيقات.

ما هي نتائج هذه الفحوصات؟

class CustomError extends Error {}
const err = new CustomError('test');
console.log(err instanceof CustomError);
console.log(err instanceof Error);
console.log(err instanceof Object);

تذكر سلسلة النموذج الأولي في وراثة JavaScript.

جميعها ترجع true. CustomError يمتد من Error، والذي يمتد من Object. عامل instanceof يتحقق من سلسلة النموذج الأولي بأكملها، لذا فإن مثيل CustomError هو أيضًا مثيل لكل من Error و Object.

افترض أن إطار iframe من الأصل نفسه أنشأ خطأ ومرره إلى الإطار الأب باسم iframeError. ماذا يمكن أن تُرجع المقارنة في الإطار الأب؟

console.log(iframeError instanceof Error);

السياقات المختلفة لها منشئات Error مختلفة.

قد تكون النتيجة false لأن لكل سياق تنفيذ مُنشئ Error خاصًا به. يمكن أن يساعد Object.prototype.toString.call(obj) في التعرف على أخطاء من سياقات مختلفة، لكن يمكن تزوير الوسم باستخدام Symbol.toStringTag؛ ليس هذا الفحص ضمانًا لأصالة الكائن.

ماذا يحدث عندما ترمي سلسلة نصية؟

try {
throw "Oops!";
} catch (e) {
console.log(e instanceof Error);
console.log(typeof e);
}

تسمح JavaScript برمي أي قيمة، وليس فقط كائنات الخطأ.

تسمح JavaScript برمي أي قيمة. هنا، e instanceof Error هو false و typeof e هو "string". هذا يمكن أن يكسر كود معالجة الأخطاء الذي يفترض أن جميع الاستثناءات الملتقطة هي كائنات خطأ. قم دائمًا برمي مثيلات Error لتحسين التصحيح.

ما هي قيمة err.name؟

class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
const err = new CustomError('test');
console.log(err.name);

انظر إلى ما ينتجه this.constructor.name.

err.name هي "CustomError" لأن this.constructor.name يعيد اسم الفئة. تعيين this.name = this.constructor.name هو نمط شائع لضمان عرض اسم الفئة المخصصة بشكل صحيح في تتبع الأخطاء ورسائل الأخطاء.

ما الناتج دون تعيين name؟

class MyError extends Error {
// No constructor or name setting
}
const err = new MyError('test');
console.log(err.name);

ماذا تحتوي خاصية name الافتراضية في Error؟

بدون تعيين this.name بشكل صريح، يرث الخطأ خاصية name الافتراضية من الكلاس Error، وهي "Error". لهذا السبب يجب على كلاسات الأخطاء المخصصة دائمًا تعيين this.name = this.constructor.name في المُنشئ الخاص بها.

ماذا تُرجع wrapper.cause.message؟

const original = new Error('Original error');
const wrapper = new Error('Wrapper',
{ cause: original }
);
console.log(wrapper.cause.message);

Error.cause هي ميزة حديثة في JavaScript لسلسلة الأخطاء.

Error.cause (ES2022) تسمح بسلسلة الأخطاء للحفاظ على سياق الخطأ الأصلي. wrapper.cause يشير إلى الخطأ الأصلي، لذا wrapper.cause.message تُرجع "Original error". هذا مفيد لتغليف الأخطاء ذات المستوى المنخفض بسياق ذي مستوى أعلى.

في V8 أو Node.js، ماذا تفعل Error.captureStackTrace؟

function createError(msg) {
const err = new Error(msg);
Error.captureStackTrace(err, createError);
return err;
}
const error = createError('test');

هذه ميزة خاصة بـ V8 لتتبعات مكدس أنظف.

Error.captureStackTrace (V8/Node.js) تزيل الدالة المحددة (createError) من تتبع المكدس، مما يجعل دوال إنشاء الأخطاء غير مرئية للمستخدمين النهائيين. هذا يخلق تتبعات مكدس أنظف تشير إلى مكان استدعاء الدالة المصنعة، وليس الدالة نفسها.

ما هي رسالة الخطأ؟

function validate(value) {
if (!value) {
throw new Error(
`Value ${value} is invalid`
);
}
}
try {
validate(undefined);
} catch (e) {
console.log(e.message);
}

كيف يتعامل القالب النصي مع القيمة undefined أثناء الاستيفاء؟

تقوم القوالب النصية بتحويل undefined إلى السلسلة "undefined" أثناء الاستيفاء. تصبح رسالة الخطأ "Value undefined is invalid". للحصول على رسائل أنظف، فكر في استخدام value ?? 'null' أو فحوصات مماثلة قبل الاستيفاء.

ما الذي يتم إرساله إلى العميل؟

// Express.js route
app.get('/api/data', (req, res) => {
const error = new Error('Database failed');
res.json({ error });
});

تذكر كيف يتم تسلسل كائنات الخطأ بواسطة JSON.stringify.

يستخدم res.json() داخليًا JSON.stringify()، لذلك يصبح كائن الخطأ {}. يتلقى العميل {"error":{}}. لإصلاح ذلك، استخدم res.json({ error: error.message }) أو res.json({ error: { message: error.message, name: error.name } }).

ماذا يمكن أن يقبل Promise.reject()؟

Promise.reject('string').catch(e =>
console.log(typeof e)
);
Promise.reject({code: 404}).catch(e =>
console.log(e.code)
);
Promise.reject(42).catch(e =>
console.log(e)
);

تعمل رفضات Promise بشكل مشابه لعبارات throw.

مثل throw، يقبل Promise.reject() أي قيمة - سلاسل نصية، كائنات، أرقام، إلخ. هذا يطبع "string" و 404 و 42. تحقق دائمًا من نوع القيم الملتقطة في سلاسل الـ Promise، خاصة عند التعامل مع كود طرف ثالث قد يرفض بقيم غير Error.

ما مدى موثوقية error.code و error.errno؟

const fs = require('fs');
fs.readFile('missing.txt', (err, data) => {
if (err) {
console.log(err.code); // 'ENOENT'
console.log(err.errno); // -2
}
});

ضع في اعتبارك بيئات JavaScript المختلفة وأنواع الأخطاء.

الخصائص مثل code و errno خاصة بالبيئة (Node.js في هذه الحالة) وليست جزءًا من كائن Error القياسي. أخطاء المتصفح لن تحتوي على هذه الخصائص. تحقق دائمًا من وجودها: if (err.code === 'ENOENT') بدلاً من افتراض وجودها.

ماذا تُرجع هذه الفحوصات؟

const fakeError = {
name: 'Error',
message: 'Fake error',
stack: 'fake stack'
};
console.log(fakeError instanceof Error);
console.log(Object.prototype.toString.call(
fakeError
) === '[object Error]');

يتتبع أحد الفحصين سلسلة النموذج الأولي؛ ويُرجع الآخر وسم كائن يمكن أن يتأثر بـ Symbol.toStringTag.

النتيجتان هما false, false. الكائن العادي لا يرث من Error.prototype، ونتيجة Object.prototype.toString.call() هنا هي [object Object]، فلا تساوي [object Error]. يمكن تزوير الوسم باستخدام Symbol.toStringTag؛ لا يثبت هذا الفحص وحده أن الكائن خطأ أصيل.

أتقن فن معالجة الأخطاء

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

هل أنت مستعد لمزيد من التحديات؟ اطلع على مجموعة الاختبارات الكاملة لمزيد من الألغاز الذهنية حول JavaScript والخوارزميات والمزيد!