محتويات الدورة

Node.js / المتوسط: حدود التطبيق / الدرس 10

التدفقات والضغط العكسي

عالج مقاطع محدودة وحافظ على انتقال الفشل واحترم مستهلكا أبطأ من المنتج.

وقت القراءة التقريبي: 4 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • اشرح المفاهيم والمفاضلات المتعلقة بموضوع «التدفقات والضغط العكسي».
  • توقع المثالين وتحقق من مخرجاتهما وسلوك تنظيف الموارد فيهما.
  • شخّص الإخفاقات الثلاثة الشائعة وطبق الحلول المشروحة على مدخلات مشابهة.

اختيار تمثيل متدفق

يعرض التدفق البيانات مع الزمن بدلا من تحميل المدخل كله. تنتج التدفقات المقروءة البيانات وتستهلكها القابلة للكتابة، وتفعل التحويلات الأمرين. يفيد ذلك للملفات الكبيرة والنقل الشبكي والمعالجة التدريجية، شرط ألا تجمع كل مرحلة المحتوى داخليا. ستعيد مصفوفة نهائية لكل المقاطع مشكلة الذاكرة. قد تسهل صيغة JSON لكل سطر المعالجة مقارنة بمصفوفة عملاقة، لكن يجب تقييد طول السطر ومعالجة السجلات المشوهة.

المقاطع وحدات نقل

لا يضمن المقطع سطرا أو محرفا أو سجلا كاملا؛ فقد يمتد محرف UTF-8 على عدة مقاطع بايتات. استخدم فاك ترميز ملائما، واحتفظ فقط بالجزء غير المكتمل الذي يحتاجه المحلل. يغير وضع الكائنات الوحدة من بايتات إلى قيم JavaScript، لكنه لا يجعل القيم صغيرة أو آمنة. اختر أسلوب استهلاك واحدا مثل التكرار غير المتزامن أو pipeline، ولا تخلط مستمعات data مع قراءات متنافسة. تمنع الملكية الواضحة ضياع المعالجة أو تكرارها.

استخدام pipeline لربط دورة الحياة

يربط المثال الأول مقروءا وتحويل أحرف ووجهة كتابة عبر pipeline القائمة على الوعود. يراقب انتظارها الاكتمال وينقل الأخطاء عبر السلسلة. تجمع الوجهة مقطعين صغيرين لإظهار المخرجات فقط؛ وينبغي لوجهة ملف كبير أن تكتب مباشرة إلى مقصدها. يجب استدعاء callback المخصص مرة واحدة بالضبط، بما في ذلك الفشل. ولا تستطيع pipeline اكتشاف استثناء وقع في عمل منفصل ولم يُبلّغ به عبر الاستدعاء أو تدمير التدفق.

javascript مثال كامل: استخدام pipeline لربط دورة الحياة
import { Readable, Transform, Writable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
const collected = [];
await pipeline(
  Readable.from(['ab', 'cd']),
  new Transform({ transform(chunk, encoding, done) { done(null, chunk.toString().toUpperCase()); } }),
  new Writable({ write(chunk, encoding, done) { collected.push(chunk.toString()); done(); } })
);
console.log(collected.join(''));
الناتج المتوقع
ABCD

تنقل pipeline المقطعين عبر التحويل ولا تنجح إلا بعد اكتمال الكتابة. يقتصر الجمع على بيانات العرض الصغيرة.

احترام امتلاء مخزن الكتابة

عندما تعيد writable.write القيمة false، يجب أن يتوقف المنتج حتى drain قبل كتابة المزيد. تعني false بلوغ المخزن عتبته لا رفض المقطع. يستخدم المثال الثاني highWaterMark صغيرة لإظهار الإشارة وينتظر drain قبل الإنهاء. العتبة ليست حدا صارما لكل تخصيص، ويحتاج المقطع الضخم نفسه إلى ذاكرة. فضّل pipeline عادة على التنسيق اليدوي، خصوصا عند تنافس الإلغاء والأخطاء مع drain. تحول الكتابة غير المحدودة بطء المستهلك إلى نفاد ذاكرة.

javascript مثال كامل: احترام امتلاء مخزن الكتابة
import { Writable } from 'node:stream';
import { once } from 'node:events';
const sink = new Writable({ highWaterMark: 1,
  write(chunk, encoding, done) { setImmediate(done); }
});
const acceptedMore = sink.write(Buffer.from('ab'));
console.log(acceptedMore);
if (!acceptedMore) await once(sink, 'drain');
const finished = once(sink, 'finish');
sink.end(); await finished;
console.log('finished');
الناتج المتوقع
false
finished

يتجاوز المقطع ذو البايتين عتبة البايت الواحد. يتيح انتظار drain تفريغ المخزن قبل إنهاء التدفق.

معالجة الفشل والإلغاء معا

يجب أن يوقف فشل الوجهة العمل السابق ويحرر المقابض أو الاتصالات. تنسق pipeline التدمير، لكنها قد تدمر اتصال استجابة HTTP قبل إرسال جسم خطأ مخصص؛ لذا اختر هذا السلوك عمدا. مرر إشارة عند دعم الإلغاء وحدد عمل كل سجل واختبر مصدرا فاشلا ووجهة بطيئة. قس الذاكرة بأحجام ممثلة بدلا من افتراض أن الواجهة تضمن حدا لها. ينظم الضغط العكسي المعدل بين المراحل ولا يغني عن حدود الطلب والمهل وعدد التدفقات المتزامنة.

الأخطاء الشائعة وإصلاحها

ترتفع الذاكرة مع عميل بطيء

لماذا يحدث؟
تجاهل المنتج القيمة false من write.
كيف تصلحه؟
استخدم pipeline أو انتظر drain مع معالجة الخطأ والإلغاء.
كيف تتجنب تكراره؟
اختبر المستهلك البطيء وميزانية الذاكرة.

تتشوه المحارف بين المقاطع

لماذا يحدث؟
فُك كل مقطع بايتات مستقلا.
كيف تصلحه؟
استخدم فاك ترميز تدريجيا أو setEncoding للمقروء.
كيف تتجنب تكراره؟
اختبر نصا متعدد البايتات مقطوعا عند كل حد.

لا تنتهي pipeline

لماذا يحدث؟
أغفلت كتابة أو تحويل مخصص استدعاء callback.
كيف تصلحه؟
استدع callback مرة واحدة في كل مسار.
كيف تتجنب تكراره؟
اختبر النجاح والفشل بمهل محدودة.

اختبر فهمك

1. ماذا تتطلب false من write، وهل قُبل المقطع؟

اعرض الحل المشروح

قُبل المقطع للمعالجة أو التخزين. أوقف إنتاج المزيد حتى drain مع مراقبة الخطأ والإلغاء. ستكرر إعادة المقطع نفسه البيانات. تدير pipeline هذا التنسيق عادة.

2. يخزن محلل تدفق كل سجل معالج في مصفوفة. هل يحد الضغط العكسي الذاكرة الكلية؟

اعرض الحل المشروح

لا. ينظم الضغط العكسي التدفق بين المراحل، لكن النتائج المحتفظ بها تنمو مع العدد الكلي. خزّن كل سجل أو احسب إحصاءات تدريجية، واحتفظ بنافذة محدودة عند الحاجة. اختبر الذاكرة مع أحجام متزايدة للتحقق من الخاصية المقصودة.

أهم ما تعلمته

  • يعرض التدفق البيانات مع الزمن بدلا من تحميل المدخل كله.
  • لا يضمن المقطع سطرا أو محرفا أو سجلا كاملا.
  • يجب أن يوقف فشل الوجهة العمل السابق ويحرر المقابض أو الاتصالات.

المراجع الرسمية