Setiap runtime yang matang punya “laci perkakas” berisi alat-alat kecil yang dipakai berulang-ulang, dan di Node.js laci itu bernama node:util. Isinya beragam — dari pemrosesan argumen command line, konversi function callback menjadi promise, format string ala printf, sampai perapian output debugging. Kita tidak akan menghafal seluruh isinya; chapter ini membedah empat anggota yang paling sering membantu pekerjaan sehari-hari: parseArgs(), promisify(), format(), serta inspect() dan styleText().

Argumen CLI dengan parseArgs() #

Command line adalah antarmuka paling alami untuk alat berbasis Node.js, tetapi argumen mentah dari process.argv datang sebagai array string tanpa struktur: mana flag, mana nilai, mana argumen posisi. Function parseArgs() memilah semuanya berdasarkan skema yang kita definisikan sendiri. Berkas parse-args.mjs menerima satu nama pengguna, satu opsi bernilai, dan satu flag:

js
import { parseArgs } from "node:util";

const { values, positionals } = parseArgs({
  allowPositionals: true,
  options: {
    prioritas: { type: "string", short: "p" },
    ringkas: { type: "boolean", short: "r" },
  },
});

console.log("positionals:", positionals);
console.log("prioritas  :", values.prioritas);
console.log("ringkas    :", values.ringkas);

Output yang Dihasilkan:

text
positionals: [ 'siti' ]
prioritas  : tinggi
ringkas    : true

Program di atas dijalankan dengan perintah node parse-args.mjs siti --prioritas=tinggi --ringkas. Mari kita telaah baris-barisnya:

  1. Baris 3-9 (parseArgs({...})): Object konfigurasi mendefinisikan dua hal. allowPositionals: true mengizinkan argumen posisi — tanpa baris ini, argumen bebas seperti siti langsung dianggap error. Property options memetakan nama opsi ke tipe dan singkatannya: prioritas bertipe "string" bisa ditulis pendek -p, sedangkan ringkas bertipe "boolean" cukup ditulis --ringkas tanpa nilai.
  2. Baris 11 (positionals): Hasil pemilahan berupa dua bagian. Array positionals berisi argumen posisi dalam urutan penulisan, dan object values berisi pasangan opsi-nilai.
  3. Baris 12-13: Nilai values.prioritas berupa string hasil pemotongan tanda =, sedangkan flag boolean menghasilkan true — bukan string "true" — sehingga siap dipakai langsung dalam kondisi if.

parseArgs() juga menjaga disiplin: opsi yang tidak terdaftar di options membuat error terlempar, sehingga salah ketik seperti --ringaks terdeteksi sejak awal. Perilaku allowPositionals sendiri bisa kita lihat langsung dengan snippet berikut:

js
import { parseArgs } from "node:util";

try {
  parseArgs({ args: ["siti"], options: {} });
} catch (error) {
  console.log(error.code);
  console.log(error.message);
}

Output yang Dihasilkan:

text
ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL
Unexpected argument 'siti'. This command does not take positional arguments

Callback ke Promise dengan promisify() #

Banyak API Node.js lama — dan banyak library pihak ketiga — masih memakai gaya callback: hasil pengiriman lewat argument terakhir sebuah function, dengan konvensi parameter pertama callback berisi error (atau null bila sukses). Di sisi lain, kode modern lebih nyaman memakai async/await. Function promisify() menjembatani keduanya dengan membungkus function callback menjadi function yang mengembalikan promise. Berkas promisify.mjs mendefinisikan function callback sendiri lalu membungkusnya:

js
import { promisify } from "node:util";

function bacaKonfigurasi(berkas, callback) {
  if (!berkas.endsWith(".json")) {
    callback(new Error("ekstensi harus .json"));
    return;
  }
  callback(null, { berkas, mode: "produksi" });
}

const bacaKonfigurasiAsync = promisify(bacaKonfigurasi);

console.log(await bacaKonfigurasiAsync("tugas.json"));

try {
  await bacaKonfigurasiAsync("catatan.txt");
} catch (error) {
  console.log("gagal:", error.message);
}

Output yang Dihasilkan:

text
{ berkas: 'tugas.json', mode: 'produksi' }
gagal: ekstensi harus .json

Mari kita telaah baris-baris kode di atas:

  1. Baris 3-9 (bacaKonfigurasi): Function sumber mengikuti konvensi callback Node.js — parameter terakhir adalah callback, dan callback dipanggil dengan new Error(...) saat gagal atau (null, hasil) saat sukses. Kondisi gagal ditangani lebih dulu, pola khas gaya callback.
  2. Baris 11 (promisify(bacaKonfigurasi)): Hasil pembungkusan adalah function baru dengan parameter yang sama minus callback. Panggilan bacaKonfigurasiAsync("tugas.json") mengembalikan promise, bukan undefined.
  3. Baris 13 (await ...): Nilai sukses tiba sebagai hasil await — object konfigurasi — tanpa perlu menyentuh callback sama sekali.
  4. Baris 15-19 (try/catch): Error yang dikirim lewat parameter pertama callback berubah menjadi promise yang ditolak, jadi penanganannya memakai catch biasa — satu jalur error yang sama dengan seluruh kode async/await kita.

Pola yang sama bekerja untuk function bawaan runtime. Contoh klasiknya setTimeout(), yang dalam gaya callback dipanggil bersama callback dan durasi; setelah dibungkus, kita mendapat function delay() yang bisa di-await:

js
import { promisify } from "node:util";

const delay = promisify(setTimeout);

console.log("sebelum");
await delay(10);
console.log("sesudah");

Output yang Dihasilkan:

text
sebelum
sesudah

Format String dengan format() #

Function format() merangkai string dengan penanda tempat (placeholder) ala printf — pola yang juga dipakai console.log secara internal. Tabel berikut merangkum penanda yang paling sering dipakai:

PenandaDiganti denganContoh masukanHasil
%sString"%s!", "Budi"Budi!
%dAngka"Beban %d%%", 85Beban 85%
%jJSON"%j", {mode: "produksi"}{"mode":"produksi"}
%oHasil util.inspect()"%o", objekrepresentasi object
%%Satu tanda persen"100%%" bersama argumen lain100%

Blok format-inspect.mjs di bawah sekaligus memakai format(), inspect(), dan styleText(); kita bedah ketiganya setelahnya:

js
import util from "node:util";

console.log(util.format("Halo, %s!", "Budi"));
console.log(util.format("Beban %d%%", 85));
console.log(util.format("Konfigurasi: %j", { mode: "produksi" }));

const dalam = { tugas: { rincian: { jumlah: 3 } } };
console.log(util.inspect(dalam));
console.log(util.inspect(dalam, { depth: 1 }));

console.log("status    :", util.styleText("green", "berhasil"));
console.log("peringatan:", util.styleText("bold", "periksa berkas"));

Output yang Dihasilkan:

text
Halo, Budi!
Beban 85%
Konfigurasi: {"mode":"produksi"}
{ tugas: { rincian: { jumlah: 3 } } }
{ tugas: { rincian: [Object] } }
status    : berhasil
peringatan: periksa berkas

Baris 3-5 memperlihatkan tiga penanda bekerja: %s untuk nama, %d untuk angka, dan %j untuk object dalam bentuk JSON — berguna saat menulis log satu baris. Penanda %% selalu berpasangan dengan argumen lain karena format() yang menerima satu argumen string saja dikembalikan apa adanya tanpa diproses.

Menata Output dengan inspect() dan styleText() #

console.log() memanggil util.inspect() di belakang layar untuk mengubah object menjadi teks, tetapi versi langsungnya memberi kita kendali penuh lewat options. Option depth membatasi kedalaman struktur yang ditampilkan: pada baris 9 berkas di atas, { depth: 1 } membuat level terdalam tampil sebagai [Object] — ringkasan yang mencegah log menjadi panjang tak berkesudahan saat struktur data bersarang dalam. Default-nya adalah depth: 2; nilai null membuka seluruh kedalaman. Option lain yang sering dipakai antara lain breakLength untuk mengatur kapan array panjang dipatahkan menjadi beberapa baris.

Sementara inspect() mengatur struktur, styleText() mengatur gaya visual. Function ini menerima nama gaya dan teks, lalu mengembalikan teks yang disisipi kode warna ANSI — misalnya "green" untuk hijau dan "bold" untuk cetak tebal; array seperti ["bold", "red"] menggabungkan beberapa gaya sekaligus. Ada dua hal penting dari perilakunya: variable lingkungan NO_COLOR mematikan seluruh gaya, dan gaya juga tidak diterapkan saat output ditujukan ke stream tanpa dukungan warna seperti pipa atau berkas. Karena contoh di atas dieksekusi dengan output yang ditangkap, blok hasilnya menampilkan teks polos — di terminal interaktif pembaca, kata berhasil tampil hijau. Perilaku otomatis inilah yang membuat styleText() aman dipakai di program CLI: pengguna yang mengalihkan output ke grep atau menyimpannya ke berkas mendapat teks bersih tanpa karakter aneh.

Empat perkakas ini baru sebagian kecil isi node:util, tetapi sudah cukup untuk membuat CLI yang rapi membaca argumen, menyambungkan API lama ke kode modern, dan menghasilkan log yang enak dibaca. Bab berikutnya berpindah ke pola yang menjadi jantung arsitektur Node.js sejak hari pertama: EventEmitter dari module node:events.

Referensi lanjutan untuk topik chapter ini: