Node.js/stream

من موسوعة حسوب
< Node.js
مراجعة 06:12، 20 نوفمبر 2018 بواسطة رهف-النجار (نقاش | مساهمات) (القسم الأول من المجرى)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)
اذهب إلى التنقل اذهب إلى البحث

الاستقرار: 2-مستقر

المجرى هو واجهة مجرّدة للعمل مع البيانات المتدفقة في Node.js. توفّر الوحدة stream واجهة برمجية (API) أساسية تجعل من السهل بناء كائنات تتعامل مع واجهة المجرى.

يوجد العديد من كائنات المجرى التي توفرها Node.js. على سبيل المثال، http.IncomingMessage (طلبيات الخادم HTTP) و process.stdout هما نسخ من الصنف stream.

يمكن أن تكون المجاري قابلة للقراءة (readable)، أو قابلة للكتابة (writable)، أو كليهما. كل المجاري هي نسخ من الصنف EventEmitter.

يمكن الوصول إلى الوحدة stream باستخدام:

const stream = require('stream');

لمَّا كان من الضروري فهم كيفية عمل المجاري، فإنَّ الوحدة stream بحد ذاتها هي أكثر فائدةً للمطورين الذين ينشئون أنواعًا جديدةً من نسخ المجاري. المطورون الذين يستهلكون كائنات المجاري بالمقام الأول نادرًا ما يحتاجون لاستخدام الوحدة stream بشكل مباشر.

تنظيم هذا المستند

يُقسّم هذا التوثيق إلى قسمين رئيسيين مع قسم ثالث للملاحظات الإضافية. يشرح القسم الأول عناصر الواجهة البرمجية للمجرى (stream API) التي تكون مطلوبة لاستخدام المجاري ضمن أي تطبيق. يشرح القسم الثاني عناصر الواجهة البرمجية (API) التي تُكون مطلوبةً لتطبيق أنواع جديدة من المجاري.

أنواع المجاري

يوجد أربعة أنواع رئيسية للمجاري ضمن Node.js هي:

  • Writable: المجاري التي يمكن أن تُكتب عليها البيانات (مثل fs.createWriteStream()‎)، و
  • Readable: المجاري التي يمكن أن تُقرأ منها البيانات. (مثل fs.createReadStream()‎‎)، و
  • Duplex: المجاري التي تجمع بين النوع Readable والنوع Writable (مثل net.Socket)
  • Transform: المجاري Duplex التي يمكن أن تعدّل أو تحوّل البيانات حسبما تُكتب أو تُقرأ (مثل zlib.createDeflate()‎).

بالإضافة إلى ذلك، تحوي هذه الوحدة الدوال الخدمية pipeline و finished.

نمط الكائن

كل المجاري المُنشأة من قبل واجهات Node.js تعمل حصريًا على السلاسل النصية والكائنات Buffer (أو Uint8Array). مع ذلك، فمن الممكن لتطبيقات المجاري أن تعمل مع أنواع بيانات أخرى في JavaScript (باستثناء النوع null، الذي يخدم غرضًا خاصًا ضمن المجاري). مثل هذه المجاري يؤخذ بالحسبان تشغيلها في "نمط الكائن" (object mode).

تُبدَّل نُسَخ المجاري إلى نمط الكائن باستخدام الخيار objectMode عند إنشاء المجرى. محاولة قلب مجرى موجود إلى نمط الكائن ليست آمنةً.

Buffering

سوف يخزن كلا المجريين Writable و Readable البيانات في مخزن مؤقت داخلي لكي يمكن استعادتها باستخدام التابع writable.writableBuffer أو readable.readableBuffer على التوالي.

كمية البيانات القابلة للتخزين تعتمد على الخيار highWaterMark المُمرر إلى باني المجرى. من أجل المجاري القياسية، يحدد الخيار highWaterMark العدد الكلي من البايتات. أمَّا من أجل المجاري المُشغلة في نمط الكائن، فيحدد الخيار highWaterMark العدد الكلي من الكائنات.

تُخزّن البيانات مؤقتًا في المجاري التي من النوع Readable عندما يستدعي التابع stream.push(chunk)‎. إذا لم يستدعي من يستخدم المجرى التابع cstream.read()‎‎، فستتوضع البيانات ضمن طابور داخلي إلى أن تُستخدَم.

حالما يصل الحجم الكلي لذاكرة القراءة الداخلية المؤقتة عتبةَ محددة من قبل highWaterMark، سيوقفُ المجرى مؤقتًا قراءة البيانات من المصادر الأساسية حتى تُستهلك البيانات الحالية المخزنة مؤقتًا (ذلك أنّ المجرى سيوقف استدعاء التابع readable._read()‎‎ المحلي الذي يُستخدم لوضع البيانات في ذاكرة القراءة المؤقتة).

تُخزّن البيانات مؤقتًا في المجاري التي من النوع Writable عندما يُستدعى التابع writable.write(chunk)‎ مرارًا وتكرارًا. طالما أنّ الحجم الكلي لذاكرة الكتابة المؤقتة الداخلية هو أدنى من العتبة المضبوطة بمقدار highWaterMark، سوف يعيد استدعاء writable.write()‎ القيمة true. حالما يصل أو يتجاوز حجم ذاكرة التخزين المؤقت الداخلية القيمة highWaterMark، سوف تُعاد القيمة false.

الهدف الأساسي من واجهات الوحدة stream البرمجية - على وجه الخصوص التابع stream.pipe()‎ - هو تقييد حجم التخزين المؤقت للبيانات إلى مستويات مقبولة من أجل التوافق بين المصادر (source) والوجهات (destination) ذات السرعات المختلفة لكي لا تمتلئ الذاكرة المتوافرة.

بما أن كلا المجريين Duplex و Transform يجمعان بين النوعين Readable و Writable، يمتلك كل واحد منها ذاكرتي تخزين مؤقتة. هاتان الذاكراتان داخليتين ومنفصلتين عن بعضهما وتستخدمان للقراءة والكتابة يف آن واحد، مما يسمح لكل طرف من المجرى بالتشغيل بشكل مستقل عن الآخر بينما يضمن تدفق بيانات مناسب وفعّال. على سبيل المثال، النُسخ net.Socket هي مجاري من النوع Duplex طرفها القابل للقراءة (Readable) يسمح باستهلاك البيانات المُستقبَلة من المقبس وطرفها القابل للكتابة (Writable) يسمح بكتابة البيانات على المقبس. بسبب أن البيانات قد تُكتب إلى المقبس بمعدل أسرع أو أبطأ من البيانات التي تُستقبل وتكتب على المجرى، فمن الضروري لكل طرف أن يعمل (ويخزِّن البيانات) بشكل مستقل عن الآخر.

الواجهات البرمجية لمستخدمي المجرى

كل تطبيقات Node.js تقريبًا، مهما كانت بسيطة، تستخدم المجاري بطريقةٍ ما. الآتي هو مثال لاستخدام المجاري في تطبيق Node.js والذي ينفّذ مُخدّم HTTP:

const http = require('http');

const server = http.createServer((req, res) => {
  


Req هو http.IncomingMessage، والذي هو مجرى قابل للقراءة //
Req هو http.ServerResponse، والذي هو مجرى قابل للكتابة //


  let body = '';


احصل على البيانات كسلاسل utf8 //
إذا لم يُجهّز الترميز ستُستقبل كائنات ذاكرة مؤقتة. //

  req.setEncoding('utf8');



تطلق المجاري القابلة للقراءة أحداث 'data' حالما يُضاف مستمع. //

  req.on('data', (chunk) => {
    body += chunk;
  });

يشير الحدث 'end' أن الجسم الكامل قد استُقبل.

  req.on('end', () => {
    try {
      const data = JSON.parse(body);
      // write back something interesting to the user:
      res.write(typeof data);
      res.end();
    } catch (er) {



أوه، json سيئة! //
      res.statusCode = 400;
      return res.end(`error: ${er.message}`);
    }
  });
});

server.listen(1337);

// $ curl localhost:1337 -d "{}"
// object
// $ curl localhost:1337 -d "\"foo\""
// string
// $ curl localhost:1337 -d "not json"
// error: Unexpected token o in JSON at position 1

المجاري ذات النوع Writable (مثل res في المثال) توفر توابع مثل write()‎ و end()‎ والتي تُستخدم لكتابة البيانات على المجرى.

المجاري ذات النوع Readable تستخدم واجهات الصنف EventEmitter البرمجية من أجل اشعار شيفرة التطبيق عندما تكون البيانات متوفرة للقراءة من المجرى. يمكن قراءة هذه البيانات المتوفرة من المجرى بعدة طرق.

تستخدم المجاري التي من النوع Writable و Readable واجهات الصنف EventEmitter البرمجية بطرق متنوعة للبقاء على اتصال دائم بالحالة الحالية للمجرى.

تذكر أن المجاري التي من النوع Duplex و Transform هي مجاري قابلة للكتاية (Writable) والقراءة (Readable).

لا يُطلب من التطبيقات التي إمّا تكتب البيانات أو تقرؤها من المجرى تنفيذ واجهات الوحدة stream بشكل مباشر، وبذلك لا يوجد عمومًا سبب لاستدعاء الوحدة stream عبر require('stream')‎‎.

ينبغي على المطورين الراغبين بإنشاء أنواع جديدة من المجاري الرجوع إلى القسم الواجهات البرمجية لمنفذي المجاري.

المجاري القابلة للكتابة

المجاري القابلة للكتابة إجمالًا تمثِّل مكانًا قابلًا لكتابة البيانات فيه.

تتضمن الأمثلة التالية مجارٍ قابلة للكتابة:

  • طلب HTTP، من طرف العميل.
  • استجابة HTTP، من طرف الخادم.
  • مجاري الوحدة fs القابلة للكتابة.
  • مجاري الوحدة zlib.
  • مجاري الوحدة crypto.
  • المقابس TCP.
  • العملية الابن للمجرى stdin (هي subprocess.stdin).
  • المجرى process.stdout و process.stderr.

بعض هذه الأمثلة هي فعليًا مجاري من النوع Duplex والتي تنفّذ الواجهة Writable.

كل المجاري التي من النوع Writable تنفّذ الواجهة المعرّفة بالصنف stream.Writable.

بينما قد تختلف نسخ المجاري التي من النوع Writable بطرق متنوعة، كل المجاري Writable تتبع نمط الاستخدام الأساسي كما هو موضّح في المثال أدناه:

const myStream = getWritableStreamSomehow();
myStream.write('some data');
myStream.write('some more data');
myStream.end('done writing data');

الصنف stream.Writable

أضيف في الإصدار: 0.9.4.

الحدث 'close'

يُطلَق الحدث 'close' عندما يكون المجرى أو أحد موارده الأساسية (واصف الملفات مثلًا) قد أُغلق. يشير هذا الحدث أنّه لن تُطلق المزيد من الأحداث، ولن تحصل المزيد من العمليات المتعلقة بالمجرى.

لا تُطلق كل المجاري Writable الحدث 'close'.

الحدث 'drain'

أُضيف في الإصدار:0.9.4.

إذا أعاد التابع stream.write(chunk)‎ القيمة false، فسوف يُطلَق الحدث 'drain' عندما يكون من المناسب استئناف كتابة البيانات على المجرى.

كتابة البيانات إلى المجرى القابل للكتابة المزوّد لمليون مرّة.//
كن منتبهًا للضغط العائد//


function writeOneMillionTimes(writer, data, encoding, callback) {
  let i = 1000000;
  write();
  function write() {
    let ok = true;
    do {
      i--;
      if (i === 0) {

آخر مرّة//

        writer.write(data, encoding, callback);
      } else {



انظر إذا كان ينبغي لنا الإستمرار أو الإنتظار//
لا تمرر دالة رد النداء لأنّنا لم ننته بعد//

        ok = writer.write(data, encoding);
      }
    } while (i > 0 && ok);
    if (i > 0) {

وجب التوقف باكرًا//
اكتب بعض المزيد حالما تنضب//

      writer.once('drain', write);
    }
  }
}
الحدث 'error'

أضيف في الإصدار: 0.9.4.

  • <Error>

يُطلق الحدث 'error' إذا حصل خطأ أثناء الكتابة على أو إرسال البيانات إلى المجرى. سوف يُمرَّر إلى دالة رد النداء المنصتة الوسيط Error فقط عند استدعائها.

لن يُغلق المجرى عندما يُطلق الحدث 'error'.

الحدث 'finish'

أضيف في الإصدار: 0.9.4.

سوف يُطلق الحدث 'finish' بعد استدعاء التابع  stream.end()‎‎، وبعد أن دُفعت كل البيانات للنظام الأساسي.

const writer = getWritableStreamSomehow();
for (let i = 0; i < 100; i++) {
  writer.write(`hello, #${i}!\n`);
}
writer.end('This is the end\n');
writer.on('finish', () => {
  console.error('All writes are now complete.');
});