اختبار: إتقان الإدخال/الإخراج في NodeJS
اختبر معرفتك بالملفات والتدفقات والمخازن المؤقتة
مستعد للغوص في عالم NodeJS IO؟ 🌊
سيختبر هذا الاختبار فهمك لعمليات الإدخال/الإخراج في Node، من عمليات نظام الملفات الأساسية إلى مفاهيم التدفق المتقدمة. سنغطي الـ buffers، الـ encoding، وأفضل الممارسات للتعامل مع البيانات بكفاءة.
دعنا نرى مدى معرفتك بالـ streams من الـ buffers! 🚀
ماذا يفعل هذا الكود؟
const buf = Buffer.alloc(5);console.log(buf);Buffer.alloc(size) ينشئ مخزنًا مؤقتًا جديدًا بالحجم المحدد مملوءًا بالأصفار.
سيكون الناتج: <Buffer 00 00 00 00 00>
أما Buffer.allocUnsafe(5) فلا يهيئ الذاكرة إلى أصفار وقد يكشف بيانات سابقة؛ ليس مولدًا لبيانات عشوائية. املأه قبل قراءة محتواه أو مشاركته.
ماذا سيطبع هذا؟
const buf = Buffer.from([65]);console.log(buf.toString());الأرقام في المصفوفة تمثل رموز ASCII:
- 65: ‘A’
toString() تحول هذه البايتات إلى تمثيلها النصي باستخدام ترميز UTF-8 بشكل افتراضي.
ما هو ترتيب الإخراج؟
import fs from 'fs';fs.readFile('test.txt', 'utf8', (err, data) => { console.log(data);});console.log('Done');بما أن readFile غير متزامن، يستمر الكود في التنفيذ أثناء قراءة الملف.
لذلك، ستتم طباعة “Done” قبل محتوى الملف.
لانتظار قراءة الملف أولاً، يمكنك استخدام النسخة المعتمدة على Promise:
import { promises as fs } from 'fs';
async function read() { const data = await fs.readFile('test.txt', 'utf8'); console.log(data); console.log('Done');}ما الذي يعيده fs.readFileSync() افتراضيًا؟
import fs from 'fs';const content = fs.readFileSync('test.txt');fs.readFileSync() يعيد كائن Buffer افتراضيًا عندما لا يتم تحديد ترميز. إذا كنت تريد سلسلة نصية، فأنت بحاجة إما إلى:
- تحديد ترميز:
fs.readFileSync('test.txt', 'utf8') - تحويل Buffer:
content.toString()
ما مجموعة الأحداث الشائعة الاستخدام مع التدفقات القابلة للقراءة؟
تصدر التدفقات القابلة للقراءة عدة أحداث مهمة:
- ‘data’: عندما تكون البيانات متاحة للقراءة
- ‘end’: عندما لا توجد بيانات أخرى للقراءة
- ‘error’: عند حدوث خطأ
- ‘close’: عند إغلاق التدفق والمورد الأساسي
const readable = fs.createReadStream('file.txt');readable.on('data', chunk => console.log(chunk));readable.on('end', () => console.log('Done!'));ماذا يفعل الخيار recursive؟
import fs from 'fs';fs.mkdirSync('./a/b/c', { recursive: true });الخيار recursive: true ينشئ الأدلة الأصلية إذا لم تكن موجودة.
بدون هذا الخيار، محاولة إنشاء './a/b/c' ستؤدي إلى خطأ إذا كان './a' أو './a/b' غير موجودين.
هذا مشابه لأمر الصدفة mkdir -p.
ماذا سيكون الناتج؟
import { Transform } from 'stream';const upperCase = new Transform({ transform(chunk, encoding, callback) { callback(null, chunk.toString().toUpperCase()); }});process.stdin .pipe(upperCase) .pipe(process.stdout);// Input: "hello world"تعدّل تدفقات التحويل (Transform streams) البيانات أثناء مرورها. تُعالج كل قطعة بيانات هنا كما يلي:
- تحويلها إلى سلسلة نصية
- تحويلها إلى أحرف كبيرة
- تمريرها إلى stdout
ينشئ هذا سلسلة معالجة تحول الإدخال إلى أحرف كبيرة.
كم مرة يتم ضمان تشغيل fs.watch() عند تعديل ملف؟
import fs from 'fs';fs.watch('test.txt', (eventType, filename) => { console.log(`${filename} was changed`);});// Then modify test.txt oncefs.watch() ليس مضمونًا للتشغيل مرة واحدة بالضبط لكل تغيير منطقي في الملف. غالبًا ما يتم تشغيله عدة مرات لأن العديد من محررات النصوص:
- تحفظ في ملف مؤقت
- تعيد تسميته إلى الملف الهدف
للحصول على مراقبة أكثر موثوقية، فكر في استخدام:
- حزمة
chokidar - إزالة الارتداد (debouncing) من رد الاتصال
- استخدام
fs.watchFile()(على الرغم من أنه أقل كفاءة)
ما هو الغرض الرئيسي من الضغط الخلفي للتدفق؟
الضغط الخلفي هو آلية تمنع تجاوز الذاكرة عن طريق إيقاف القراءة مؤقتًا عندما لا تستطيع نهاية الكتابة مواكبة ذلك.
مثال على الضغط الخلفي اليدوي:
readable.on('data', (chunk) => { const canContinue = writable.write(chunk); if (!canContinue) { readable.pause(); writable.once('drain', () => readable.resume()); }});pipe() يتعامل مع هذا تلقائيًا!
ماذا يفعل هذا الكود؟
import fs from 'fs';fs.symlinkSync('target.txt', 'link.txt');symlinkSync ينشئ رابطًا رمزيًا (مثل اختصار) للملف الهدف.
الاختلافات الرئيسية عن الروابط الصلبة:
- يمكن الربط بالمجلدات
- يمكن أن يمتد عبر أنظمة الملفات
- ينكسر إذا تم حذف الهدف
لإنشاء رابط صلب بدلاً من ذلك:
fs.linkSync('target.txt', 'hardlink.txt');ما هي الأوضاع التي يمكن أن تعمل بها تدفقات Node.js؟
يمكن للتدفقات أن تعمل في:
- الوضع الثنائي (الافتراضي): للمخازن المؤقتة والسلاسل النصية
- وضع الكائن: لقيم JavaScript باستثناء
null، فهي إشارة نهاية التدفق
مثال على وضع الكائن:
import { Transform } from 'stream';const objectStream = new Transform({ objectMode: true, transform(chunk, encoding, callback) { callback(null, { value: chunk }); }});آمل أنك استمتعت باختبار معرفتك بـ NodeJS IO! هل تريد المزيد؟ اطّلع على مجموعة الاختبارات لمزيد من التحديات!
