Setiap program command line diwariskan tiga saluran komunikasi oleh sistem operasi: satu untuk masukan dan dua untuk keluaran. Saluran inilah yang membuat alat-alat Unix bisa dirangkai menjadi pipeline — output satu program mengalir menjadi input program berikutnya. Node.js menyediakan ketiganya sebagai process.stdin, process.stdout, dan process.stderr. Pada chapter ini kita memisahkan hasil dari peringatan ke stream yang benar, membaca jawaban pengguna lewat readline, lalu memproses seluruh masukan yang dipipa dari program lain.
Tiga Saluran Warisan Sistem Operasi #
Ketiga stream ini berusia lebih tua dari Node.js sendiri dan dikenali sistem operasi lewat nomor file descriptor:
| Stream | FD | Arah | Object Node.js | Isi yang tepat |
|---|---|---|---|---|
stdin | 0 | masuk | process.stdin | masukan dari pengguna atau program lain |
stdout | 1 | keluar | process.stdout | hasil program — data yang diminta pengguna |
stderr | 2 | keluar | process.stderr | peringatan dan pesan error |
console.log() menulis ke stdout, console.error() dan console.warn() menulis ke stderr, sedangkan process.stdout.write() menulis ke stdout tanpa menambahkan baris baru. Berkas tiga-stream.mjs memakai ketiganya sekaligus:
console.log("baris hasil normal");
process.stdout.write("progres");
process.stdout.write(" sepotong-sepotong");
console.log(" selesai");
console.error("peringatan: format berkas tidak standar");
console.error("kesalahan : berkas kosong");Output yang Dihasilkan:
baris hasil normal
progres sepotong-sepotong selesai
peringatan: format berkas tidak standar
kesalahan : berkas kosongMari kita bedah hasilnya. Baris pertama dan terakhir stdout biasa dihasilkan console.log(). Tiga potongan di tengah — "progres", " sepotong-sepotong", " selesai" — nyambung menjadi satu baris karena process.stdout.write() tidak menambahkan baris baru; console.log(" selesai") turut menyambung karena output-nya ditulis sebelum ganti baris. Perilaku ini pas untuk indikator progres yang terus tertimpa. Dua baris terakhir ditulis console.error() ke stderr — pada eksekusi di atas keduanya tampil berurutan setelah stdout.
Memisahkan Hasil dari Peringatan #
Pemisahan dua stream keluaran baru terasa manfaatnya saat output diarahkan. Notasi 2>/dev/null membuang seluruh isi stderr ke “lubang nol” sehingga hanya hasil yang tampil:
node tiga-stream.mjs 2>/dev/nullOutput yang Dihasilkan:
baris hasil normal
progres sepotong-sepotong selesaiKedua baris peringatan tidak ikut tersimpan — hanya hasil yang tampak. Sebaliknya, 2>&1 menggabungkan stderr ke stdout bila kita justru ingin merekam semuanya ke satu berkas log. Inilah alasan disiplin stream penting: program yang mencampur pesan error ke stdout merusak pipeline seperti node alat.mjs data.csv | sort, karena sort ikut memproses peringatan seolah-olah hasil. Dengan disiplin, pengguna memilih sendiri mau melihat keduanya atau hasilnya saja.
Bertanya ke Pengguna dengan readline #
Untuk program interaktif, module node:readline/promises membaca jawaban baris demi baris dari stdin dalam gaya async/await. Berkas sapa.mjs mengajukan satu pertanyaan:
import * as readline from "node:readline/promises";
import { stdin, stdout } from "node:process";
const rl = readline.createInterface({ input: stdin, output: stdout });
const nama = await rl.question("Siapa namamu? ");
rl.close();
console.log(`Selamat belajar, ${nama}!`);Output yang Dihasilkan:
Siapa namamu? Selamat belajar, Budi!Mari kita telaah baris-per-baris kode di atas:
- Baris 1-2 (import):
readline/promisesadalah versi promise dari module readline, sedangkanstdindanstdoutdiambil darinode:processagar tidak mengetikprocess.stdinberulang. - Baris 4 (
createInterface): objectrlmengikat satu pasangan input-output; pertanyaan ditulis keoutputdan jawaban dibaca dariinput. - Baris 6 (
await rl.question): pertanyaan dicetak, lalu eksekusi berhenti sampai pengguna menekan Enter. Hasilnya adalah satu baris jawaban tanpa tanda akhir baris. - Baris 7 (
rl.close()): antarmuka ditutup agar event loop tidak menunggu masukan lagi dan proses bisa berakhir.
Saat dijalankan di terminal sungguhan, ketikan pengguna terlihat menimpa pertanyaan. Pada output terekam di atas, jawaban Budi tidak digaungkan kembali karena stdin berasal dari pipa, bukan terminal interaktif — sehingga pertanyaan dan sapaan tampak menempel satu baris. Perilaku programnya sama; hanya tampilannya yang berbeda.
Membaca Seluruh stdin yang Dipipa #
Pola kedua pembacaan stdin justru mengabaikan sifat interaktif: program membaca seluruh masukan hingga tanda akhir berkas, lalu memprosesnya sekaligus. process.stdin adalah async iterable, sehingga loop for await mengumpulkan seluruh potongan masukan:
let teks = "";
for await (const potongan of process.stdin) {
teks += potongan;
}
const baris = teks.split("\n").filter((b) => b.trim() !== "");
console.log(`menerima ${baris.length} baris`);
for (const [indeks, isi] of baris.entries()) {
console.log(`${indeks + 1}. ${isi}`);
}Jalankan dengan memipakan teks dari echo atau printf:
node hitung.mjsOutput yang Dihasilkan:
menerima 3 baris
1. Budi
2. Siti
3. DewiPerintah yang tampak di blok di atas adalah bentuk interaktifnya — program menunggu kita mengetik baris demi baris lalu menekan Ctrl+D sebagai tanda akhir masukan. Output terekam dihasilkan dari pemipaan printf 'Budi\nSiti\nDewi\n' | node hitung.mjs yang mengirim tiga baris sekaligus ke stdin; hasilnya identik dengan mengetik manual. Loop for await berakhir saat stdin menutup, filter() membuang baris kosong di akhir, lalu setiap baris dicetak dengan nomor urut.
Masuk dan keluar program kini kita kendalikan. Satu hal terakhir yang dilihat dunia luar dari program kita adalah angka exit code — laporan ringkas satu angka yang menentukan apakah pipeline melanjutkan atau berhenti. Chapter berikutnya membahasnya tuntas.
Referensi lanjutan untuk topik chapter ini: