Ketika program berperilaku tidak sesuai harapan, refleksi pertama kita biasanya menaburkan console.log di sana-sini lalu menjalankan ulang kode sambil berharap bisa menebak letak masalah. Cara ini kadang berhasil, tetapi cepat melelahkan: kita harus menebak variabel mana yang patut dicurigai, mengubah kode, lalu mengulang seluruh proses. Node.js menyediakan jalan yang jauh lebih presisi lewat inspector bawaan: eksekusi bisa dijeda di baris mana pun, nilai variabel diperiksa pada saat itu juga, lalu eksekusi dilanjutkan langkah demi langkah. Pada chapter ini kita akan memakai flag node --inspect, menyambungkannya ke Chrome DevTools dan VS Code, serta memanfaatkan pernyataan debugger dan modul node:inspector.
Keterbatasan Menebak dengan console.log #
console.log memaksa kita menebak terlebih dahulu: nilai apa yang dicurigai, di baris mana curiganya muncul. Setiap tebakan baru berarti mengedit kode dan menjalankan ulang program dari awal. Untuk kode yang membaca berkas atau memanggil API, pengulangan seperti ini lambat dan mengganggu alur berpikir.
Debugger membalik prosesnya. Alih-alih kita yang mengejar informasi, program berhenti di titik yang kita pilih dan menyerahkan seluruh kondisinya: isi variabel, urutan pemanggilan function, sampai nilai ekspresi yang bisa kita coba langsung. Informasi sebesar itu didapat tanpa mengubah satu baris kode pun.
Mengaktifkan Inspector dengan –inspect #
Flag --inspect memerintahkan proses Node.js membuka pendengar khusus di alamat 127.0.0.1 port 9229. Proses tetap berjalan normal; yang berubah, kini ada “pintu” tempat perkakas debug seperti Chrome DevTools dapat masuk dan berbicara memakai Chrome DevTools Protocol. Jalankan perintah berikut pada proyek Anda:
node --inspect hitung.mjsTerminal akan menampilkan pesan pembuka berikut sebelum keluaran program:
Debugger listening on ws://127.0.0.1:9229/0f2c936f-b1cd-4ac9-aab3-f63b0f33d55e
For help, see: https://nodejs.org/learn/getting-started/debugging
total: 60Selain --inspect polos, ada beberapa variasi flag yang memengaruhi titik mulai proses:
| Flag | Perilaku |
|---|---|
--inspect | Aktifkan inspector; proses berjalan normal sejak awal |
--inspect-brk | Aktifkan inspector; jeda sebelum baris kode pertama dijalankan |
--inspect-wait | Aktifkan inspector; tunda eksekusi sampai debugger terpasang |
--inspect=host:port | Tentukan sendiri alamat dan port pendengar |
Proses yang sudah berjalan tanpa flag juga bisa diaktifkan inspectornya dengan mengirim sinyal SIGUSR1, meskipun cara ini tidak tersedia di Windows. Rincian seluruh flag tersedia di dokumentasi CLI
.
Menyambungkan Chrome DevTools #
Chrome dan Edge memiliki antarmuka debug yang lengkap dan bisa tersambung ke proses Node.js mana pun yang membuka inspector. Alurnya seperti berikut:
- Jalankan program dengan
node --inspect hitung.mjs. - Buka alamat
chrome://inspectdi browser. - Klik tombol Configure dan pastikan
127.0.0.1:9229tercantum. - Proses kita muncul di daftar Remote Target; klik inspect padanya.
Setelah tersambung, panel Sources menampilkan kode kita dan menerima klik pada nomor baris untuk memasang breakpoint. Saat eksekusi berhenti, panel Scope memperlihatkan isi variabel, panel Call Stack memperlihatkan rantai pemanggilan, dan tombol Step over, Step into, serta Step out menggerakkan eksekusi satu langkah demi satu langkah. Console di panel tersebut juga bisa dipakai mengevaluasi ekspresi dalam konteks titik jeda — misalnya mencoba daftar.length tanpa perlu mengedit kode.
Menyambungkan VS Code #
VS Code memiliki debugger Node.js bawaan yang berbicara dengan protocol yang sama, jadi tidak ada yang perlu dipasang. Buka panel Run and Debug, klik ikon roda gigi untuk membuat berkas .vscode/launch.json, lalu pilih lingkungan Node.js:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jalankan hitung.mjs",
"program": "${workspaceFolder}/hitung.mjs"
}
]
}Tekan F5 dan VS Code akan menjalankan berkas tersebut dengan inspector aktif, lengkap dengan titik jeda, panel variabel, dan kendali langkah yang sama seperti DevTools. Menyalakan breakpoint cukup dengan klik di samping nomor baris.
Pernyataan debugger sebagai Titik Jeda #
Kadang kita sudah tahu persis baris yang ingin dijeda, sehingga memasang breakpoint lewat UI terasa cukup. Namun ada kalanya titik jeda lebih enak dituliskan langsung di kode — misalnya di tengah loop yang hanya bermasalah pada iterasi tertentu. JavaScript menyediakan pernyataan debugger untuk keperluan ini. Perhatikan contoh berikut:
function hitungSubtotal(daftar) {
debugger; // diabaikan ketika tidak ada inspector yang terpasang
return daftar.reduce((a, b) => a + b, 0);
}
console.log(hitungSubtotal([10000, 25000, 15000]));Output yang Dihasilkan:
50000Mari kita telaah baris-baris kode di atas:
- Baris 2 (
debugger;): pernyataan titik jeda. Ketika tidak ada inspector yang terpasang, pernyataan ini diabaikan sepenuhnya — program berjalan seolah baris tersebut tidak ada. - Baris 3 (
return daftar.reduce(...)): menjumlahkan seluruh elemen array denganreduce. - Baris 6 (
console.log(...)): mencetak hasil50000tanpa hambatan apa pun, bukti bahwadebuggermemang tidak berdampak pada eksekusi normal.
Begitu program yang sama dijalankan dengan --inspect dan sebuah debugger terpasang, eksekusi akan berhenti tepat di baris debugger tersebut — menunggu kita memeriksa keadaan lalu melanjutkan.
Inspector dari Dalam Kode dengan node:inspector #
Protokol debug yang dipakai Chrome DevTools ternyata bisa dipanggil langsung dari kode kita lewat modul node:inspector. Class Session pada modul tersebut memungkinkan program memasang “debugger mini” pada dirinya sendiri: menerima kejadian jeda, membaca informasi bingkai pemanggilan, lalu melanjutkan eksekusi. Contoh berikut menangkap setiap titik jeda yang dipicu pernyataan debugger:
import { Session } from "node:inspector";
const sesi = new Session();
const titikJeda = [];
function kirim(metode, params = {}) {
return new Promise((resolve, reject) => {
sesi.post(metode, params, (err, hasil) => (err ? reject(err) : resolve(hasil)));
});
}
sesi.on("Debugger.paused", (pesan) => {
const frame = pesan.params.callFrames[0];
const namaFungsi = frame.functionName || "(anonim)";
titikJeda.push(`${namaFungsi} baris ${frame.location.lineNumber + 1}`);
sesi.post("Debugger.resume");
});
sesi.connect();
await kirim("Debugger.enable");
function hitungSubtotal(daftar) {
debugger;
return daftar.reduce((a, b) => a + b, 0);
}
function hitungTotal(diskon) {
const subtotal = hitungSubtotal([10000, 25000, 15000]);
debugger;
return subtotal - diskon;
}
const total = hitungTotal(5000);
console.log(`total belanja: ${total}`);
console.log("berhenti di:", titikJeda.join(" lalu "));Output yang Dihasilkan:
total belanja: 45000
berhenti di: hitungSubtotal baris 23 lalu hitungTotal baris 29Mari kita telaah bagian-bagian pentingnya:
- Baris 3 (
new Session()): membuat sesi protocol baru yang akan berbicara dengan inspector proses ini sendiri. - Baris 6-10 (
function kirim(...)): pembungkus promise untuksesi.postagar perintah protocol sepertiDebugger.enablebisa dipanggil denganawait. - Baris 12-17 (
sesi.on("Debugger.paused", ...)): setiap kali eksekusi berhenti, handler ini membaca bingkai teratascallFrames[0]— nama function dan lokasi barisnya — menyimpannya ke arraytitikJeda, lalu melanjutkan eksekusi denganDebugger.resume. - Baris 19-20 (
sesi.connect()danDebugger.enable): menyambungkan sesi dan menyalakan domainDebuggeragar kejadian jeda mulai dikirim. - Baris 23 dan 29 (
debugger;): dua titik jeda di dua function berbeda; keduanya tercatat pada urutan kemunculannya.
Layar kita sekarang memperlihatkan bagaimana eksekusi menyusuri hitungSubtotal lebih dahulu, lalu hitungTotal — persis alur yang digambar panel Call Stack pada DevTools.
Menjeda Ketika Error Terjadi #
Mencari letak error kadang lebih mudah bila program berhenti otomatis pada saat error itu terjadi, bukan pada baris yang kita tebak. DevTools menyediakan tombol Pause on exceptions untuk itu; lewat protocol, perintahnya bernama Debugger.setPauseOnExceptions. Contoh berikut menyalakannya sebelum memanggil function yang melempar error:
import { Session } from "node:inspector";
const sesi = new Session();
let laporanJeda = null;
function kirim(metode, params = {}) {
return new Promise((resolve, reject) => {
sesi.post(metode, params, (err, hasil) => (err ? reject(err) : resolve(hasil)));
});
}
sesi.on("Debugger.paused", (pesan) => {
const frame = pesan.params.callFrames[0];
laporanJeda = {
alasan: pesan.params.reason,
fungsi: frame.functionName,
baris: frame.location.lineNumber + 1,
};
sesi.post("Debugger.resume");
});
sesi.connect();
await kirim("Debugger.enable");
await kirim("Debugger.setPauseOnExceptions", { state: "all" });
function ambilHarga(item) {
return item.harga;
}
try {
console.log(ambilHarga(null));
} catch (err) {
console.log(`tertangkap : ${err.name}: ${err.message}`);
}
console.log(`jeda pada : fungsi ${laporanJeda.fungsi}, baris ${laporanJeda.baris}`);
console.log(`alasan : ${laporanJeda.alasan}`);Output yang Dihasilkan:
tertangkap : TypeError: Cannot read properties of null (reading 'harga')
jeda pada : fungsi ambilHarga, baris 27
alasan : exceptionPerhatikan tiga hal dari contoh ini:
- Baris 24 (
setPauseOnExceptions): menerimastateyang bisa berupa"none","uncaught", atau"all"; kita memilih"all"supaya error yang tertangkap pun memicu jeda. - Baris 26-28 (
ambilHarga): function ini akan melemparTypeErrorkarena dipanggil dengannull. - Baris 36-38 (laporan jeda): kejadian jeda tercatat dengan
reasonbernilaiexceptionpada function dan baris tempat error berasal — informasi yang sama dengan yang ditampilkan panel Breakpoints di DevTools sebelum eksekusi dilanjutkan ke blokcatch.
Dengan inspector, berburu error berubah dari menebak-menebak menjadi mengamati. Keterampilan penyelidikan berikutnya berada di luar debugger: membaca jejak yang ditinggalkan error pada keluaran terminal, atau yang biasa disebut stack trace — topik Chapter Membaca Stack Trace.