تدفقات Node.js: FileStream، Pipes، والأحداث
⚡ ملخص ذكي
تقوم تدفقات Node.js بقراءة وكتابة البيانات في أجزاء صغيرة بدلاً من تحميل الملفات بأكملها في الذاكرة، وتشرح هذه الصفحة إنشاء تدفق الملفات، وربط الأنابيب، ومعالجة الضغط الخلفي، ونموذج الأحداث الذي يحافظ على كفاءة نقل البيانات على نطاق واسع.

أنواع التدفقات في Node.js
تندرج جميع تدفقات البيانات التي يوفرها Node.js ضمن أحد الأنواع الأساسية الأربعة، ومعرفة نوع البيانات التي تُرجعها واجهة برمجة التطبيقات (API) تُوضح كيفية عمل هذا التدفق قبل كتابة أي كود. تُحدد وحدة التدفقات (stream) المُضمنة في Node.js هذه التصنيفات، وتعتمد عليها جميع واجهات برمجة التطبيقات الخاصة بالملفات والشبكات والضغط المستخدمة في هذه المقالة.
| تيار نوع | اتجاه البيانات | مثال على واجهة برمجة التطبيقات | حالة الاستخدام المشترك |
|---|---|---|---|
| Readable | المصدر فقط (تدفق البيانات للخارج) | fs.createReadStream() | قراءة ملف، واستلام نص طلب HTTP |
| للكتابة | الوجهة فقط (تدفق البيانات للداخل) | fs.createWriteStream() | كتابة ملف، وإرسال استجابة HTTP |
| دوبلكس | كلا الاتجاهين، بشكل مستقل | net.Socket (مقبس TCP) | الاتصال الشبكي ثنائي الاتجاه |
| تحول | في كلا الاتجاهين، يتم تعديل البيانات أثناء النقل. | zlib.createGzip() | ضغط أو ترميز البيانات في منتصف خط المعالجة |
يُعدّ كلٌّ من التدفق القابل للقراءة والتدفق القابل للكتابة عنصرين أساسيين يُستخدمان في جميع أنحاء هذه المقالة؛ وكلاهما يُشغّل أيضًا خادم ويب HTTP في Node.js. يجمع دفق الإرسال المزدوج كلا الدورين في كائن واحد، بينما دفق التحويل هو دفق مزدوج يقوم إخراجه بتعديل مدخلاته، مثل ضغط gzip المطبق في منتصف الدفق.
تدفق الملفات في Node.js
بعد تحديد فئات التدفق الأربعة، يقوم الجزء المتبقي من هذه المقالة بإنشاء عملية نقل ملفات باستخدام واجهات برمجة تطبيقات التدفق القابلة للقراءة والكتابة.
تستخدم العقدة التدفقات على نطاق واسع كآلية لنقل البيانات.
على سبيل المثال، عندما تقوم بإخراج أي شيء إلى وحدة التحكم باستخدام وظيفة console.log، فإنك في الواقع تستخدم دفقًا لإرسال البيانات إلى وحدة التحكم.
يتمتع Node.js أيضًا بالقدرة على دفق البيانات من الملفات بحيث يمكن قراءتها وكتابتها بشكل مناسب. سنلقي نظرة الآن على مثال لكيفية استخدام التدفقات للقراءة والكتابة من الملفات. نحن بحاجة إلى اتباع الخطوات المذكورة أدناه لهذا المثال
الخطوة 1) قم بإنشاء ملف يسمى data.txt والذي يحتوي على البيانات أدناه. لنفترض أن هذا الملف مخزن على محرك الأقراص D لجهازنا المحلي.
البرنامج التعليمي على Node.js
المقدمة
الفعاليات
اتصال البيانات
باستخدام الياسمين
الخطوة 2) اكتب الكود ذي الصلة الذي سيستفيد من التدفقات لقراءة البيانات من الملف.
var fs = require("fs"); var stream; stream = fs.createReadStream("D://data.txt"); stream.on("data", function(data) { var chunk = data.toString(); console.log(chunk); });
Code تفسير:-
- نحتاج أولاً إلى تضمين وحدات "fs" التي تحتوي على جميع الوظائف المطلوبة لإنشاء التدفقات.
- بعد ذلك، نقوم بإنشاء مجرى قابل للقراءة باستخدام الطريقة createReadStream. كمدخل، نعطي موقع ملف data.txt الخاص بنا.
- تعتبر الدالة steam.on معالجًا للأحداث، حيث نقوم بتحديد المعلمة الأولى باسم "البيانات". هذا يعني أنه عندما تأتي البيانات في الدفق من الملف، قم بتنفيذ وظيفة رد الاتصال. في حالتنا، نقوم بتعريف وظيفة رد اتصال والتي ستنفذ خطوتين أساسيتين. الأول هو تحويل البيانات المقروءة من الملف كسلسلة. والثاني هو إرسال السلسلة المحولة كمخرجات إلى وحدة التحكم.
- نحن نأخذ كل جزء من البيانات التي تتم قراءتها من دفق البيانات وتحويلها إلى سلسلة.
- وأخيرًا، نقوم بإرسال مخرجات كل قطعة محولة من السلسلة إلى وحدة التحكم.
الإخراج:
- إذا تم تنفيذ التعليمات البرمجية بشكل صحيح، فسترى الإخراج أعلاه في وحدة التحكم. سيكون هذا الإخراج هو نفسه الموجود في ملف data.txt.
الكتابة إلى ملف
بنفس الطريقة، التي ننشئ بها دفق قراءة، يمكننا أيضًا إنشاء دفق كتابة لكتابة البيانات إلى ملف. لنقم أولاً بإنشاء ملف فارغ بدون محتويات يسمى data.txt. لنفترض أن هذا الملف موجود في محرك الأقراص D بجهاز الكمبيوتر الخاص بنا.
يوضح الكود أدناه كيف يمكننا كتابة البيانات إلى الملف.
var fs = require("fs"); var stream; stream = fs.createWriteStream("D://data.txt"); stream.write("Tutorial on Node.js") stream.write("Introduction") stream.write("Events") stream.write("Generators") stream.write("Data Connectivity") stream.write("Using Jasmine")
Code تفسير:-
- نقوم بإنشاء دفق قابل للكتابة باستخدام الطريقة - createWriteStream. كمدخل، نعطي موقع ملف data.txt الخاص بنا.
- بعد ذلك استخدمنا طريقةstream.write لكتابة أسطر مختلفة من النص إلى ملفنا النصي. سيهتم الدفق بكتابة هذه البيانات إلى ملف data.txt.
إذا قمت بفتح ملف data.txt، فسترى الآن البيانات التالية في الملف
البرنامج التعليمي على Node.js
المقدمة
الفعاليات
مولدات كهرباء
اتصال البيانات
باستخدام الياسمين
الأنابيب في Node.js
تُعد قراءة وكتابة التدفقات بشكل مستقل أمرًا مفيدًا، ولكن Node.js يسمح أيضًا للتدفق القابل للقراءة بالاتصال مباشرة بالتدفق القابل للكتابة، ونقل البيانات دون الحاجة إلى رمز التخزين المؤقت اليدوي.
داخل تطبيقات العقدة، يمكن توصيل التدفقات معًا باستخدام طريقة Pipe()، والتي تأخذ وسيطتين:
- دفق قابل للكتابة مطلوب يعمل كوجهة للبيانات و
- كائن اختياري يستخدم لتمرير الخيارات.
مثال نموذجي لاستخدام الأنابيب، إذا كنت تريد نقل البيانات من ملف إلى آخر.
لذلك دعونا نرى مثالاً لكيفية نقل البيانات من ملف إلى آخر باستخدام الأنابيب.
الخطوة 1) قم بإنشاء ملف يسمى datainput.txt والذي يحتوي على البيانات أدناه. لنفترض أن هذا الملف مخزن على محرك الأقراص D لجهازنا المحلي.
البرنامج التعليمي على Node.js
المقدمة
الفعاليات
مولدات كهرباء
اتصال البيانات
باستخدام الياسمين
الخطوة 2) قم بإنشاء ملف فارغ يسمى dataOutput.txt وضعه على محرك الأقراص D بجهازك المحلي.
الخطوة 3) اكتب الكود أدناه لتنفيذ عملية نقل البيانات من ملف datainput.txt إلى ملف dataOutput.txt.
var fs = require("fs"); var readStream = fs.createReadStream("D://datainput.txt"); var writeStream = fs.createWriteStream("D://dataOutput.txt"); readStream.pipe(writeStream);
Code تفسير:-
- نقوم أولاً بإنشاء "دفق قراءة" لملف datainput.txt الخاص بنا والذي يحتوي على جميع بياناتنا التي يجب نقلها إلى الملف الجديد.
- نحتاج بعد ذلك إلى إنشاء "دفق كتابة" لملف dataOutput.txt الخاص بنا، وهو ملفنا الفارغ وهو الوجهة لنقل البيانات من ملف datainput.txt.
- نستخدم بعد ذلك أمر توجيه الإخراج لنقل البيانات من مجرى القراءة إلى مجرى الكتابة. سيأخذ أمر توجيه الإخراج جميع البيانات التي تأتي في مسار القراءة، ويدفعها إلى مسار الكتابة.
إذا قمت الآن بفتح ملف dataOutput.txt، فسترى جميع البيانات التي كانت موجودة في ملف datainput.txt.
أحداث البث والضغط العكسي في Node.js
إن استدعاء pipe()، كما هو موضح أعلاه، يخفي آليتين مهمتين على نطاق واسع: الأحداث التي يصدرها كل تدفق، ونظام الضغط العكسي الذي يمنع المصدر السريع من إغراق الوجهة البطيئة.
- بيانات: يتم إصدارها بواسطة دفق قابل للقراءة في كل مرة تتوفر فيها قطعة جديدة.
- ينتهي: يتم إصدارها بمجرد أن يقوم دفق قابل للقراءة بتسليم آخر جزء منه.
- إنهاء: يتم إصدارها بواسطة دفق قابل للكتابة بمجرد تفريغ جميع البيانات الموجودة في قائمة الانتظار.
- خطأ: يتم إصداره بواسطة أي نوع من أنواع التدفق عند الفشل؛ يجب التعامل معه دائمًا وإلا ستتعطل العملية.
يستمع المثال أدناه إلى جميع الأحداث الأربعة مع إدارة الضغط الخلفي يدويًا، وهو نفس السلوك الذي توفره الدالة pipe() تلقائيًا.
var fs = require("fs"); var readStream = fs.createReadStream("D://data.txt"); var writeStream = fs.createWriteStream("D://dataOutput.txt"); readStream.on("data", function(chunk) { var ok = writeStream.write(chunk); if (!ok) { readStream.pause(); } }); writeStream.on("drain", function() { readStream.resume(); }); readStream.on("end", function() { writeStream.end(); }); writeStream.on("finish", function() { console.log("Write completed."); }); readStream.on("error", function(err) { console.log(err); });
يحدث التداخل العكسي عندما ينتج دفق قابل للقراءة بيانات أسرع من قدرة دفق قابل للكتابة على استهلاكها. تُرجع الدالة write() القيمة false بمجرد امتلاء مخزنها المؤقت، لذا يُوقف الكود أعلاه المصدر مؤقتًا حتى يُصدر الوجهة حدث تفريغ، مما يُشير إلى أنه من الآمن استئنافه. تُنفذ الدالة pipe() هذه الدورة تلقائيًا.
⚠ نصيحة: في بيئة الإنتاج، يُفضل استخدام دالة pipeline() من وحدة stream بدلاً من سلسلة pipe() اليدوية، لأنها تقوم بإعادة توجيه الأخطاء من كل تدفق وإغلاق مقابض الملفات تلقائيًا.
الأحداث في Node.js
تُعدّ التدفقات مثالاً على تصميم Node.js الأوسع نطاقاً والقائم على الأحداث. يتناول الجزء المتبقي من هذه المقالة كيفية عمل الأحداث بشكل عام، باستخدام فئة EventEmitter نفسها التي تعتمد عليها التدفقات داخلياً.
الأحداث هي أحد المفاهيم الأساسية في Node.js وفي بعض الأحيان يُشار إلى Node.js باسم إطار عمل مدفوع بالأحداث.
في الأساس، الحدث هو شيء يحدث. على سبيل المثال، إذا تم إنشاء اتصال بقاعدة بيانات، فسيتم تشغيل حدث اتصال قاعدة البيانات. البرمجة المدفوعة بالأحداث هي إنشاء وظائف سيتم تشغيلها عند تشغيل أحداث معينة.
دعونا نلقي نظرة على مثال أساسي لتعريف حدث في Node.js.
سنقوم بإنشاء حدث يسمى "data_received". عند تشغيل هذا الحدث، سيتم إرسال النص "تم استلام البيانات" إلى وحدة التحكم.
var events = require('events'); var eventEmitter = new events.EventEmitter(); eventEmitter.on('data_received', function() { console.log('data received succesfully.'); }); eventEmitter.emit('data_received');
Code تفسير:-
- استخدم الدالة المطلوبة لتضمين وحدة "الأحداث". باستخدام هذه الوحدة، ستتمكن من إنشاء أحداث في Node.js.
- إنشاء باعث أحداث جديدة. يُستخدم هذا لربط الحدث، وهو في حالتنا "data_received" بوظيفة رد الاتصال التي تم تعريفها في الخطوة 3.
- نحدد دالة تعتمد على الحدث والتي تنص على أنه إذا تم تشغيل الحدث "data_received"، فيجب علينا إخراج النص "data_received" إلى وحدة التحكم.
- أخيرًا، لدينا مشغل يدوي للحدث باستخدام الدالة eventsEmiter.emit. سيؤدي هذا إلى تشغيل الحدث data_received.
عند تشغيل البرنامج، سيتم إرسال النص "تم استلام البيانات" إلى وحدة التحكم كما هو موضح أدناه.
انبعاث الأحداث
بالإضافة إلى إرسال الأحداث والاستماع إليها، يوفر Node.js طرقًا إضافية للتحكم في كيفية تسجيل المستمعين وفحصهم.
عند تعريف الأحداث، هناك طرق مختلفة للأحداث التي يمكن استدعاؤها. ويركز هذا الموضوع على النظر في كل واحد منهم بالتفصيل.
- معالجات الأحداث لمرة واحدة
في بعض الأحيان قد تكون مهتمًا بالتفاعل مع حدث ما فقط في المرة الأولى التي يحدث فيها. في هذه الحالات، يمكنك استخدام الأسلوب مرة واحدة ().
دعونا نرى كيف يمكننا الاستفادة من الطريقة ذات مرة لمعالجات الأحداث.
Code تفسير:-
- نحن هنا نستخدم الطريقة "مرة واحدة" لنقول أنه بالنسبة للحدث "data_received"، يجب تنفيذ وظيفة رد الاتصال مرة واحدة فقط.
- نحن هنا نقوم بتشغيل الحدث "data_received" يدويًا.
- عندما يتم تشغيل الحدث "data_received" مرة أخرى، لن يحدث شيء هذه المرة. وذلك بسبب الخطوة الأولى التي قلنا فيها أنه لا يمكن تشغيل الحدث إلا مرة واحدة.
إذا تم تنفيذ التعليمات البرمجية بشكل صحيح، فسيكون الإخراج في السجل "تم استلام البيانات بنجاح". ستظهر هذه الرسالة مرة واحدة فقط في وحدة التحكم.
- فحص مستمعي الأحداث
في أي وقت من حياته، يمكن أن يكون لباعث الحدث صفر أو أكثر من المستمعين المرتبطين به. يمكن فحص المستمعين لكل نوع حدث بعدة طرق.
إذا كنت مهتمًا فقط بتحديد عدد المستمعين المرفقين، فلا تنظر إلى أبعد من الطريقة EventEmitter.listenerCount() .
(ملاحظة: المستمعون مهمون لأن البرنامج الرئيسي يجب أن يعرف ما إذا كان تتم إضافة المستمعين سريعًا إلى حدث ما، وإلا فسيتعطل البرنامج لأنه سيتم استدعاء مستمعين إضافيين.)
Code تفسير:-
- نحن نحدد نوع EventEmitter المطلوب لاستخدام التوابع المتعلقة بالحدث.
- نقوم بعد ذلك بتعريف كائن يسمى باعث والذي سيتم استخدامه لتحديد معالجات الأحداث لدينا.
- نحن نقوم بإنشاء معالجين للأحداث لا يفعلان شيئًا في الأساس. يتم الحفاظ على هذا الأمر بسيطًا في مثالنا فقط لإظهار كيفية عمل طريقةlisterCount.
- الآن، عندما تقوم باستدعاء طريقةlisterCount في حدث data_received الخاص بنا، فسوف ترسل عدد مستمعي الأحداث المرفقين بهذا الحدث في سجل وحدة التحكم.
إذا تم تنفيذ التعليمات البرمجية بشكل صحيح، فسيتم عرض القيمة 2 في سجل وحدة التحكم.
- حدث المستمع الجديد
في كل مرة يتم فيها تسجيل معالج حدث جديد، يقوم باعث الحدث بإصدار حدث مستمع جديد. يتم استخدام هذا الحدث للكشف عن معالجات الأحداث الجديدة. عادةً ما تستخدم حدث newListener عندما تحتاج إلى تخصيص الموارد أو تنفيذ بعض الإجراءات لكل معالج حدث جديد.
var events = require('events'); var eventEmitter = events.EventEmitter; var emitter = new eventEmitter(); emitter.on("newListener", function(eventName, listener) { console.log("Added listener for " + eventName + " events"); }); emitter.on('data_received', function() {}); emitter.on('data_received', function() {});
Code تفسير:-
- نقوم بإنشاء معالج حدث جديد لحدث "newListener". لذا، كلما تم تسجيل معالج حدث جديد، سيتم عرض النص "Added listener for" + اسم الحدث في وحدة التحكم.
- نحن هنا نكتب إلى وحدة التحكم النص "تمت إضافة مستمع لـ" + اسم الحدث لكل حدث مسجل.
- نحن نحدد معالجي أحداث لحدثنا "data_received".
إذا تم تنفيذ التعليمات البرمجية أعلاه بشكل صحيح، فسيتم عرض النص أدناه في وحدة التحكم. إنه يوضح فقط أنه تم تشغيل معالج الأحداث "newListener" مرتين.
تمت إضافة مستمع لأحداث data_received
تمت إضافة مستمع لأحداث data_received









