راهنمای جامع Streams در Node.js (ماژول node:stream)

ماژول node:stream یکی از بنیادی‌ترین بخش‌های Node.js است و یک رابط انتزاعی برای کار با داده‌های جریان‌محور فراهم می‌کند. Streams برای مدیریت ورودی/خروجی‌های حجیم، درخواست‌های شبکه، خواندن/نوشتن فایل‌ها و انجام تبدیل‌های داده‌ای ضروری هستند. Streams نمونه‌هایی از EventEmitter بوده و در چهار نوع اصلی ارائه می‌شوند: Readable، Writable، Duplex و Transform.

Readable / Writable / Duplex / Transformpipeline / finishedBackpressureObject ModeCustom StreamsAsync IterationWeb Streams Interop

~3 دقیقه مطالعه • بروزرسانی ۹ دی ۱۴۰۴

1. معرفی Streams


Streams در Node.js امکان پردازش داده‌ها به‌صورت تکه‌تکه و پیوسته را فراهم می‌کنند. این رویکرد باعث کاهش مصرف حافظه و افزایش کارایی در عملیات I/O می‌شود.


2. انواع Stream


  • Readable: منبع داده (مثل fs.createReadStream یا پاسخ HTTP).
  • Writable: مقصد داده (مثل fs.createWriteStream یا درخواست HTTP).
  • Duplex: هم خواندنی و هم نوشتنی (مثل TCP Socket).
  • Transform: نوعی Duplex که داده را تغییر می‌دهد (مثل zlib).

3. دسترسی به ماژول


const stream = require('node:stream');
// یا
import stream from 'node:stream';

4. API مبتنی بر Promise


از طریق require('node:stream/promises') قابل دسترسی است.


4.1. pipeline()


برای اتصال چند Stream با مدیریت خطا و پاکسازی مناسب استفاده می‌شود.


await pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('input.txt.gz')
);

4.2. finished()


منتظر پایان یک Stream می‌ماند.


await finished(readableStream);

5. Object Mode


در حالت عادی Streams فقط Buffer یا string را مدیریت می‌کنند. در Object Mode می‌توان هر مقدار جاوااسکریپتی (به‌جز null) را ارسال کرد.


const readable = new stream.Readable({
  objectMode: true,
  read() {
    this.push({ value: 42 });
    this.push(null);
  }
});

6. Buffering و Backpressure


  • هر Stream یک بافر داخلی دارد.
  • highWaterMark تعیین می‌کند چه زمانی Backpressure اعمال شود.
  • write() اگر false برگرداند یعنی باید منتظر رویداد drain بمانید.

7. API برای مصرف‌کنندگان


7.1. Writable Streams


  • write(chunk)
  • end()
  • رویدادها: drain, finish, error, pipe

7.2. Readable Streams


دو حالت دارند:

  • Paused: باید read() فراخوانی شود.
  • Flowing: رویداد data به‌صورت خودکار منتشر می‌شود.

7.3. Async Iteration


for await (const chunk of readable) {
  console.log(chunk);
}

8. Duplex و Transform


  • Duplex: خواندن و نوشتن مستقل.
  • Transform: خروجی بر اساس ورودی تغییر می‌کند.

9. API برای پیاده‌سازی Streamهای سفارشی


9.1. Writable سفارشی


class MyWritable extends stream.Writable {
  _write(chunk, encoding, callback) {
    // پردازش داده
    callback();
  }
}

9.2. Readable سفارشی


class Counter extends stream.Readable {
  _read() {
    this.push('data');
    this.push(null);
  }
}

9.3. Transform سفارشی


class Uppercase extends stream.Transform {
  _transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  }
}

10. PassThrough


نوعی Transform که داده را بدون تغییر عبور می‌دهد.


11. توابع کمکی


  • stream.duplexPair(): ساخت یک جفت Duplex متصل.
  • stream.addAbortSignal(): اتصال AbortSignal به Stream.
  • Readable.from(): ساخت Stream از iterable.
  • سازگاری با Web Streams: toWeb() و fromWeb().

12. بهترین شیوه‌ها


  • استفاده از pipeline() برای زنجیره‌سازی.
  • مدیریت صحیح Backpressure.
  • استفاده از Object Mode فقط در صورت نیاز.
  • استفاده از async iteration برای خواندن داده.
  • فعال‌سازی Source Maps با --enable-source-maps.

نتیجه‌گیری


Streams ستون فقرات کارایی I/O در Node.js هستند. با تسلط بر آن‌ها می‌توان برنامه‌هایی مقیاس‌پذیر، سریع و کم‌مصرف ساخت که به‌خوبی با فایل‌ها، شبکه و داده‌های حجیم کار می‌کنند.


نوشته و پژوهش شده توسط دکتر شاهین صیامی