Dua chapter pertama Section ini memberi kita Buffer sebagai wadah byte yang lentur. Namun wadah saja tidak cukup ketika data yang harus diproses lebih besar daripada memori yang tersedia, atau ketika data datang sedikit demi sedikit dari jaringan yang belum selesai mengirim. Untuk kedua situasi tersebut, Node.js menyediakan stream: abstraksi untuk memproses data secara bertahap, potongan demi potongan, sejak data pertama tiba. Chapter ini membangun pemahaman konseptual tentang stream sebelum kita mempraktikkan tiap jenisnya pada chapter-chapter berikutnya.

Keterbatasan readFile #

Method readFile memuat seluruh isi berkas ke memori dalam satu operasi. Pendekatan ini sederhana dan cukup untuk berkas kecil, tetapi menjadi beban pada data besar. Perbandingan berikut memperlihatkan letak masalahnya:

AspekreadFileStream
MemoriSeluruh berkas berada di memori sekaligusHanya beberapa potongan aktif di memori
Waktu tunggu data pertamaSetelah seluruh berkas selesai dibacaSegera setelah potongan pertama tiba
Ukuran data yang realistisBerkas kecil hingga sedangUkuran berapa pun, termasuk data tanpa ujung

Bayangkan menyalin berkas video berukuran ratusan megabyte dengan readFile: proses menyediakan ratusan megabyte memori, menunggu seluruh byte masuk, baru kemudian menulis ke tujuan. Dengan stream, potongan pertama yang telah dibaca langsung ditulis ke tujuan sementara potongan berikutnya masih dalam perjalanan. Memori yang terpakai hanya sebesar beberapa potongan, dan total waktu proses mendekati waktu baca berkas. Seluruh konsep stream Node.js dirangkum pada dokumentasi resmi stream .

Empat Jenis Stream #

Seluruh stream di Node.js merupakan turunan dari empat class dasar di module node:stream. Perbedaan keempatnya terletak pada arah aliran data:

JenisArah aliranContoh nyata
ReadableSumber data keluarfs.createReadStream(), permintaan HTTP masuk
WritableTujuan data masukfs.createWriteStream(), process.stdout
DuplexDua arah secara terpisahKoneksi net.Socket
TransformMasuk lalu keluar berubahzlib.createGzip(), stream enkripsi

Readable dan Writable adalah fondasinya: yang satu menghasilkan data, yang satu menerimanya. Duplex menggabungkan keduanya dalam satu object — sisi masuk dan sisi keluarnya beroperasi independen, seperti koneksi socket yang mengirim dan menerima data bersamaan. Transform adalah Duplex istimewa yang mengubah data di tengah perjalanan: byte yang masuk diproses, lalu hasilnya keluar dari sisi yang sama.

Aliran Data dari Sumber ke Tujuan #

Kerja stream dibayangkan sebagai saluran yang menghubungkan sumber dengan tujuan, dengan pengolah opsional di tengahnya:

text
Sumber data                    Tujuan data
┌──────────┐     potongan     ┌──────────┐
| Readable | ───────────────> | Writable |
└──────────┘                  └──────────┘

┌──────────┐     ┌───────────┐     ┌──────────┐
| Readable | ──> | Transform | ──> | Writable |
└──────────┘     └───────────┘     └──────────┘
      sumber          pengubah         tujuan

┌──────────────────────────────┐
|            Duplex            |
└──────────────────────────────┘
  masuk ──>  ──────────  <── keluar
        (dua saluran terpisah)

Setiap panah pada diagram mengangkut potongan data — buffer kecil berisi beberapa kilobyte, bukan berkas utuh. Model penggeraknya berbasis event: stream Readable memancarkan event data setiap kali potongan siap dan event end ketika data habis, sementara stream Writable memancarkan drain dan finish untuk menandakan keadaan internalnya. Panduan lengkap pola konsumsi kedua jenis event ini tersedia pada bagian API for stream consumers . Kedua sisi ini juga bisa disambung otomatis dengan method pipe(), yang akan kita bahas tuntas pada Chapter pipe dan pipeline.

Contoh berikut memastikan hubungan keempat class tersebut di atas mesin nyata, lalu menjalankan alur terpendek: Readable disambungkan ke Writable:

js
import { Readable, Writable, Duplex, Transform } from "node:stream";

const contohDuplex = new Duplex({
  read() {
    this.push(null);
  },
  write(_potongan, _encoding, callback) {
    callback();
  },
});

console.log("Duplex adalah Readable :", contohDuplex instanceof Readable);
console.log("Duplex adalah Writable :", contohDuplex instanceof Writable);
console.log("Transform adalah Duplex :", new Transform() instanceof Duplex);

const sumber = Readable.from(["belajar ", "stream "]);
const saluran = new Writable({
  write(potongan, _encoding, callback) {
    process.stdout.write(potongan);
    callback();
  },
});

sumber.pipe(saluran).on("finish", () => {
  console.log("-> selesai");
});

Output yang Dihasilkan:

text
Duplex adalah Readable : true
Duplex adalah Writable : true
Transform adalah Duplex : true
belajar stream -> selesai

Mari kita telaah baris-baris kode di atas:

  1. Baris 3-10 (new Duplex({...})): sebuah Duplex dibuat dengan dua method wajib — read() untuk sisi Readable dan write() untuk sisi Writable. Di sini keduanya sengaja diisi versi paling sederhana: push(null) menandakan sumber sudah habis, dan callback() menyatakan penulisan selesai.
  2. Baris 12-14 (instanceof): operator instanceof menguji rantai prototype. Hasil true menegaskan bahwa Duplex mewarisi Readable dan Writable sekaligus, serta Transform mewarisi Duplex.
  3. Baris 16-22 (new Writable({...})): method write() milik saluran menampung setiap potongan yang datang, menuliskannya ke stdout, lalu memanggil callback() sebagai tanda potongan tersebut selesai diproses.
  4. Baris 24 (sumber.pipe(saluran)): pipe() mengalirkan data dari sumber ke saluran secara otomatis. Method ini mengembalikan stream tujuan sehingga event finish bisa didaftarkan berantai di baris yang sama.

Standar Stream Web #

Selain stream klasik di atas, Node.js juga menyediakan Web Streams API yang mengikuti standar WHATWG dari platform web. Keduanya bertemu pada kebutuhan yang sama, tetapi API-nya berbeda: stream web memakai class ReadableStream dan WritableStream dengan mekanisme reader dan writer. Kode browser modern yang memakai Web Streams bisa berjalan di Node.js tanpa perubahan — rinciannya pada dokumentasi Web Streams . Untuk lingkungan Node.js murni, stream klasik tetap menjadi perkakas utama karena seluruh core module — fs, net, http, zlib, crypto — memakainya.

Kerangka konseptual ini cukup sebagai peta jalan. Langkah praktis pertama kita adalah sisi penghasil data: cara mengonsumsi Readable stream melalui event, async iterator, hingga membaca berkas bertahap — semuanya menanti di Chapter Readable Stream.