Node.js / الأساسي: بيئة تشغيل Node.js / الدرس 4
الملفات والمسارات وJSON
اقرأ ملفات بيانات صغيرة واكتبها بأمان مع التمييز بين البايتات والنصوص والمسارات وبيانات التطبيق المتحقق منها.
في هذه الصفحة
أهداف الدرس
- اشرح المفاهيم والمفاضلات المتعلقة بموضوع «الملفات والمسارات وJSON».
- توقع المثالين وتحقق من مخرجاتهما وسلوك تنظيف الموارد فيهما.
- شخّص الإخفاقات الثلاثة الشائعة وطبق الحلول المشروحة على مدخلات مشابهة.
الملفات تحتوي بايتات
يخزن نظام الملفات بايتات وليس كائنات JavaScript. تعيد readFile دون ترميز كائن Buffer، ويؤدي طلب utf8 إلى فك النص. تحوّل JSON.parse النص إلى قيم، ثم تحدد خطوة تحقق مستقلة صلاحيتها لعقد التطبيق. هذه ثلاث عمليات بأخطاء مختلفة؛ فقد تكون JSON صحيحة لكنها تحتوي حقولا أو أنواعا غير مناسبة. تناسب القراءة الكاملة ملفات الإعدادات الصغيرة الموثوقة. أما الملفات الكبيرة أو غير الموثوقة فتحتاج إلى حد للحجم أو صيغة تدفق لتجنب تخصيص ذاكرة بحجم غير محدود.
حل المسارات بوعي
يُفسّر المسار النسبي عادة انطلاقا من process.cwd() الذي قد يختلف بحسب طريقة بدء البرنامج. يمكن تحديد مورد نسبة إلى الوحدة باستخدام new URL مع import.meta.url. تجمع path.join المقاطع وفق نظام التشغيل، لكنها لا تمنح الإذن للوصول إلى موقع اختاره المستخدم. لا تفترض أن ضم مجلد أساسي إلى نص غير موثوق يمنع تجاوز المجلد. فضّل أسماء يولدها الخادم أو بحثا بمعرف مقيد. وقد تتجاوز الروابط الرمزية والتغييرات المتزامنة الفحوص البسيطة، لذا راع الأذونات ومن يستطيع تعديل المجلد.
الكتابة والقراءة والتنظيف
ينشئ المثال الأول مجلدا مؤقتا معزولا، ويحوّل كائنا صغيرا إلى نص ويكتبه ثم يقرأه بترميز صريح. يتحقق من الحقل قبل استخدامه ويحذف المجلد داخل finally. تثبت المخرجات حفظ القيمة خلال الذهاب والإياب، ولا تثبت بقاء الملف بعد تعطل الجهاز. انتظر كل عملية تعتمد عليها التالية كي لا تسبق القراءة الكتابة. لا تشغّل هذه العروض على ملف مهم قائم. وتجعل ملكية المجلد المؤقت التنظيف متوقعا وتمنع اعتماد تشغيل على آخر.
import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const dir = await mkdtemp(join(tmpdir(), 'json-'));
try {
const file = join(dir, 'settings.json');
await writeFile(file, JSON.stringify({ retries: 2 }), 'utf8');
const data = JSON.parse(await readFile(file, 'utf8'));
if (!Number.isInteger(data.retries)) throw new Error('Invalid retries');
console.log(data.retries);
} finally { await rm(dir, { recursive: true, force: true }); }
2
يوفر الترميز نصا لـ JSON.parse. يأتي التحقق من الحقل بعد تحليل الصياغة، وتحذف finally المجلد الذي يملكه المثال فقط.
معالجة أخطاء الملفات المتوقعة
تعرض أخطاء الملفات رموزا مستقرة مثل ENOENT وEACCES. افحص الرمز لتقرير استخدام قيمة افتراضية عند غياب ملف اختياري، ولا تطابق رسالة كاملة تختلف حسب النظام. يلتقط المثال الثاني غياب الملف فقط ويعيد رمي الأخطاء الأخرى. قد يخفي تحويل كل خطأ قراءة إلى بيانات فارغة فشل الأذونات أو خللا في النشر. لا تعد فحص الوجود ثم القراءة ضمانا؛ فقد يتغير الملف بين العمليتين. نفّذ العملية المطلوبة وعالج نتيجتها الفعلية.
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const dir = await mkdtemp(join(tmpdir(), 'missing-'));
try {
try { await readFile(join(dir, 'absent.json'), 'utf8'); }
catch (error) {
if (error.code !== 'ENOENT') throw error;
console.log('optional file absent');
}
} finally { await rm(dir, { recursive: true, force: true }); }
optional file absent
يبدأ المجلد فارغا يقينا. ويختار الرمز ENOENT وحده سلوك الملف الاختياري الغائب.
معرفة حدود التخزين في الملفات
تفيد كتابة JSON في التمارين والتصدير وبعض الأدوات المحلية، لكنها ليست نظام معاملات تلقائيا. قد يقرأ طلبان القيمة نفسها ثم يكتبان فوق الملف فتضيع إحدى التحديثات. قد تقلل الكتابة في ملف مؤقت ثم إعادة تسميته خطر الاستبدال الجزئي، لكن ضمانات الديمومة واختلاف الأنظمة تحتاج إلى تحليل، ويظل التنسيق بين العمليات مسألة مستقلة. استخدم قاعدة بيانات معاملات عندما تهم التحديثات المتزامنة والاستعادة. أغلق مقابض الملفات المفتوحة يدويا داخل finally، وعرّف عقد نجاح الحفظ وتحقق منه.
الأخطاء الشائعة وإصلاحها
ترمي JSON.parse خطأ SyntaxError
- لماذا يحدث؟
- النص JSON غير مكتمل أو يتضمن صياغة غير مدعومة.
- كيف تصلحه؟
- أبلغ عن المحتوى المشوه واحفظ دليلا دون تسجيل أسرار.
- كيف تتجنب تكراره؟
- استخدم JSON.stringify واختبر المدخلات المقطوعة والمشوهة.
ظهور ENOENT في مجلد عمل مختلف
- لماذا يحدث؟
- اعتمد المسار النسبي على موضع تشغيل Node.
- كيف تصلحه؟
- استخدم مجلدا أساسيا مقصودا أو رابطا نسبة إلى الوحدة.
- كيف تتجنب تكراره؟
- اختبر التشغيل من مجلد آخر.
اختفاء تحديث
- لماذا يحدث؟
- كتبت عمليات قراءة وتعديل وكتابة متزامنة فوق بعضها.
- كيف تصلحه؟
- استخدم المعاملات أو تصميما مناسبا بكاتب واحد.
- كيف تتجنب تكراره؟
- اختبر التحديثات المتنافسة ووثّق ضمانات التخزين.
اختبر فهمك
1. يحتوي ملف JSON على {"retries":"2"}. هل يقبله المثال الأول؟
اعرض الحل المشروح
لا. ينجح التحليل لكن retries يبقى نصا، فتعيد Number.isInteger القيمة false. ارفضه ما لم يسمح المخطط الموثق بالتحويل صراحة. صحة الصياغة وصحة بيانات العمل نقطتا تحقق منفصلتان، ويغير التحويل الصامت عقد الملف.
2. اشرح لماذا لا يضمن فحص الوجود ثم القراءة نجاح القراءة.
اعرض الحل المشروح
قد يحذف طرف آخر الملف أو يستبدله بعد فحص الوجود. استدع readFile مباشرة، والتقط ENOENT عندما يكون الغياب متوقعا، وأعد رمي الأخطاء غير المرتبطة. يقلل ذلك السباق ويبني القرار على العملية المهمة فعليا.
أهم ما تعلمته
- يخزن نظام الملفات بايتات وليس كائنات JavaScript.
- يُفسّر المسار النسبي عادة انطلاقا من process.cwd() الذي قد يختلف بحسب طريقة بدء البرنامج.
- تفيد كتابة JSON في التمارين والتصدير وبعض الأدوات المحلية، لكنها ليست نظام معاملات تلقائيا.