fs/promises هي النسخة المبنية على الوعود من وحدة fs في Node. دوالها (readFile وwriteFile وreaddir وstat وmkdir وrm وrename وغيرها) تعيد وعدًا بدلًا من أخذ callback، فتنتظرها بـ await وتلتقط الأخطاء بـ try/catch. استوردها بـ require('fs/promises') أو import fs from 'fs/promises'.
يطبع [ 'write docs', 'ship it' ]: كل await ينتظر عملية واحدة قبل أن يعمل السطر التالي، فتحدث الإضافة دائمًا بعد الكتابة الأولى. في ES module يعمل await في المستوى الأعلى؛ أما في CommonJS فغلّف الشيفرة بدالة async.
الدوال التي ستستخدمها أكثر
الأسماء مطابقة لواجهة callback، دون الـ callback. تغطي صفحتا readFile وwriteFile هاتين الدالتين بالتفصيل.
| الدالة | ماذا تفعل | يتحقق الوعد بـ |
|---|---|---|
readFile(path, 'utf8') | تقرأ ملفًا كاملًا | المحتوى |
writeFile(path, data) | تنشئ ملفًا أو تستبدله | undefined |
appendFile(path, data) | تضيف إلى النهاية | undefined |
readdir(path) | تسرد محتويات مجلد | مصفوفة أسماء |
stat(path) | الحجم والتواريخ، ملف أم مجلد | كائن Stats |
mkdir(path, { recursive: true }) | تنشئ مجلدات | أول مجلد أُنشئ، أو undefined |
rename(from, to) | تنقل أو تعيد التسمية | undefined |
rm(path, { recursive: true, force: true }) | تحذف ملفًا أو مجلدًا | undefined |
سرد مجلد عبر readdir وstat
تعيد readdir الأسماء فقط. استدعِ stat على كل اسم لتعرف حجمه أو لتميّز الملفات عن المجلدات، أو مرّر { withFileTypes: true } لتحصل على مدخلات تعرف نوعها مسبقًا.
تطبع الحلقة README.md 6 bytes وnotes.txt 3 bytes وsrc folder. ترفض stat الوعد بـ ENOENT لمسار غير موجود، فتصبح فحصًا سريعًا للوجود حين تريد التفاصيل أيضًا.
إعادة التسمية والنقل والحذف
تعيد rename تسمية الملف وتنقله أيضًا (داخل القرص نفسه). وتحذف rm؛ وتحتاج recursive: true للمجلد الذي فيه محتويات، وforce: true كي لا تفشل حين يكون المسار قد زال أصلًا.
ينتقل الملف من drafts إلى published باسمه الجديد، وبعد rm لا يسرد المجلد blog إلا published. احذف force: true من rm الثانية فيُرفض الوعد بـ ENOENT.
التعامل مع الأخطاء عبر try/catch
العملية الفاشلة ترفض وعدها. ومع await يصبح هذا الرفض استثناءً تلتقطه كأي استثناء آخر. والخاصية code في الخطأ تخبرك بما حدث.
لا يجد الاستدعاء الأول الملف فيعيد []؛ ويطبع الثاني Ada. رموز يجب أن تعرفها: ENOENT (لا يوجد ملف كهذا)، وEEXIST (موجود مسبقًا)، وEISDIR (مجلد حيث كان المتوقع ملفًا)، وENOTEMPTY (المجلد ما زال فيه محتويات)، وEACCES (لا صلاحية).
الوعد الذي لا تنتظره بـ await ولا تضيف إليه .catch() يصبح رفضًا غير معالج (unhandled rejection) حين يفشل، فيخرج Node بخطأ. انتظر كل استدعاء، أو أعد وعده إلى مستدعٍ ينتظره.
قراءة عدة ملفات في الوقت نفسه عبر Promise.all
حلقة for مع await تقرأ الملفات واحدًا تلو الآخر. حين لا تعتمد القراءات على بعضها، ابدأها كلها وانتظر مرة واحدة بـ Promise.all: يعمل القرص عليها في الوقت نفسه.
تحافظ Promise.all على ترتيب مصفوفة المدخلات، فتكون النتيجة [ 'alpha', 'beta', 'gamma' ] أيًا كانت القراءة التي انتهت أولًا، والطول بعد الضم هو 14. غيّر 'b.txt' في المصفوفة إلى 'x.txt' فيُرفض await كله بـ ENOENT.
إذا احتجت كل النتائج حتى حين تفشل بعض القراءات، فاستخدم Promise.allSettled؛ وتغطي صفحة الوعود الاثنتين.
أخطاء شائعة
الخلط بين الوحدتين. require('fs').readFile تريد callback؛ وrequire('fs/promises').readFile تعيد وعدًا. استدعاء نسخة callback دون callback يرمي The "cb" argument must be of type function.
نسيان await. const text = fs.readFile(...) وعد معلّق، وtext.split يرمي text.split is not a function.
الكتابة المتوازية إلى ملف واحد. Promise.all مع عدة عمليات كتابة إلى المسار نفسه قد تترك محتوى مختلطًا. نفّذها بالتتابع.
الأسئلة الشائعة
ما الفرق بين fs.promises وfs/promises؟
لا فرق. require('fs').promises وrequire('fs/promises') هما الكائن نفسه. والمسار fs/promises (أو node:fs/promises) هو الطريقة المعتادة لاستيراده اليوم.
هل توجد existsSync في fs/promises؟
لا. في واجهة الوعود توجد access، التي ترفض الوعد حين يكون المسار غائبًا، لكن النمط المعتاد هو أن تستدعي العملية التي تحتاجها وتلتقط ENOENT. وما زالت existsSync من وحدة fs العادية تعمل للفحوص السريعة.
هل fs/promises أبطأ من واجهة callback؟
قليلًا، بسبب كلفة الوعود، لكنها لا تُذكر مقارنة بوقت القرص. اختر بحسب سهولة القراءة؛ وواجهة الوعود هي التي تستخدمها معظم الشيفرة الجديدة.
كيف أحوّل دالة fs تعمل بـ callback إلى وعد؟
نادرًا ما تحتاج إلى ذلك، لأن fs/promises تغطي دوال الملفات. أما واجهات callback الأخرى، فتعيد util.promisify(fn) نسخة منها تعيد وعدًا.