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:
| Aspek | readFile | Stream |
|---|---|---|
| Memori | Seluruh berkas berada di memori sekaligus | Hanya beberapa potongan aktif di memori |
| Waktu tunggu data pertama | Setelah seluruh berkas selesai dibaca | Segera setelah potongan pertama tiba |
| Ukuran data yang realistis | Berkas kecil hingga sedang | Ukuran 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:
| Jenis | Arah aliran | Contoh nyata |
|---|---|---|
Readable | Sumber data keluar | fs.createReadStream(), permintaan HTTP masuk |
Writable | Tujuan data masuk | fs.createWriteStream(), process.stdout |
Duplex | Dua arah secara terpisah | Koneksi net.Socket |
Transform | Masuk lalu keluar berubah | zlib.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:
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:
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:
Duplex adalah Readable : true
Duplex adalah Writable : true
Transform adalah Duplex : true
belajar stream -> selesaiMari kita telaah baris-baris kode di atas:
- Baris 3-10 (
new Duplex({...})): sebuahDuplexdibuat dengan dua method wajib —read()untuk sisiReadabledanwrite()untuk sisiWritable. Di sini keduanya sengaja diisi versi paling sederhana:push(null)menandakan sumber sudah habis, dancallback()menyatakan penulisan selesai. - Baris 12-14 (
instanceof): operatorinstanceofmenguji rantai prototype. Hasiltruemenegaskan bahwaDuplexmewarisiReadabledanWritablesekaligus, sertaTransformmewarisiDuplex. - Baris 16-22 (
new Writable({...})): methodwrite()miliksaluranmenampung setiap potongan yang datang, menuliskannya ke stdout, lalu memanggilcallback()sebagai tanda potongan tersebut selesai diproses. - Baris 24 (
sumber.pipe(saluran)):pipe()mengalirkan data darisumberkesaluransecara otomatis. Method ini mengembalikan stream tujuan sehingga eventfinishbisa 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.