Stream: Memproses Data Sedikit demi Sedikit
Readable, Writable, Transform, pipe, dan pipeline: cara Node menangani data besar dengan memori kecil.
Masalah: berkas 2 GB
fs.readFile memuat seluruh berkas ke memori. Berkas 2 GB berarti 2 GB RAM — server bisa kehabisan memori. Stream memproses data dalam potongan kecil (chunk): baca sedikit, proses, kirim, lanjut. Memori yang dipakai tetap kecil, berapa pun ukuran datanya. Analogi: menuang air dari galon lewat selang ke gelas-gelas, bukan mengangkat seluruh galon.
Empat jenis stream
| Jenis | Fungsi | Contoh |
|---|---|---|
| Readable | Sumber data | fs.createReadStream, request HTTP masuk |
| Writable | Tujuan data | fs.createWriteStream, response HTTP |
| Duplex | Keduanya | Soket TCP |
| Transform | Duplex yang mengubah data | Kompresi (gzip), enkripsi, parsing |
Membaca stream
const fs = require('fs');
const r = fs.createReadStream('besar.txt', { encoding: 'utf8', highWaterMark: 64 * 1024 });
let n = 0;
r.on('data', (chunk) => { n++; /* proses potongan */ });
r.on('end', () => console.log('selesai,', n, 'chunk'));
r.on('error', (err) => console.error(err.message));
highWaterMark menentukan ukuran chunk (default 64 KB untuk berkas). Event penting: data, end, error, close.
pipe dan pipeline
// Menyalin berkas tanpa memuatnya ke memori
const { pipeline } = require('stream');
pipeline(
fs.createReadStream('asal.txt'),
fs.createWriteStream('salinan.txt'),
(err) => {
if (err) console.error('Gagal:', err.message);
else console.log('Selesai');
}
);
pipeline lebih aman daripada a.pipe(b): ia meneruskan error dan membersihkan semua stream bila salah satunya gagal. Versi Promise: const { pipeline } = require('stream/promises'); await pipeline(a, b, c);
Transform: mengubah data di tengah jalan
const { Transform } = require('stream');
const hurufBesar = new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString().toUpperCase()); // (error, data keluar)
},
});
pipeline(fs.createReadStream('in.txt'), hurufBesar, fs.createWriteStream('OUT.txt'), (err) => { /* ... */ });
Readable.from: stream dari iterable
const { Readable } = require('stream');
const r = Readable.from(['halo ', 'dunia']);
for await (const potongan of r) console.log(potongan);
Writable sendiri
const { Writable } = require('stream');
const hasil = [];
const kumpul = new Writable({
write(chunk, enc, callback) { hasil.push(chunk.toString()); callback(); },
});
Backpressure
Jika sumber lebih cepat daripada tujuan, data menumpuk di memori. Stream memiliki mekanisme backpressure: write() mengembalikan false saat buffer penuh, dan sumber berhenti sampai event drain. pipe dan pipeline mengurusnya otomatis — alasan lain mengapa jangan menulis loop write manual tanpa memeriksa nilai kembaliannya.
Di mana stream dipakai?
- Mengunggah/mengunduh berkas besar, streaming video.
- Mengirim response HTTP sedikit demi sedikit.
- Membaca log atau CSV besar baris demi baris.
- Kompresi (
zlib), enkripsi.