Engine JavaScript hanya melempar galat ketika menemui hal yang secara bahasa tidak mungkin dikerjakan: membaca properti dari null, memanggil nilai yang bukan function, dan sejenisnya. Banyak kondisi lain yang juga jelas tidak valid justru lolos tanpa suara. Diskon sebesar 250 persen, jumlah item negatif, atau nama pelanggan berisi string kosong — semuanya angka dan string biasa di mata engine, sehingga program berjalan “sukses” sambil membawa data yang tidak masuk akal.

Statement throw mengisi celah itu: kita yang memutuskan suatu kondisi tidak boleh diterima, lalu menghentikan alur di tempat itu secara eksplisit. Chapter ini membahas kapan melempar itu tepat, pola penempatannya di dalam function, dan jenis nilai apa saja yang boleh dilempar.

Kapan Program Perlu Melempar #

Aturannya sederhana: sebuah function melempar galat ketika syarat untuk mengerjakan tugasnya tidak terpenuhi. Fungsi penghitung diskon membutuhkan persen diskon di antara 0 dan 100; bila nilainya di luar rentang itu, hasil hitungan tidak bermakna — lebih baik berhenti di sini daripada melanjutkan dengan angka keliru.

Mekanisme di balik throw:

  • Eksekusi baris yang memuat throw menghentikan sisa pekerjaan di posisi itu — baris-baris setelahnya tidak dijalankan.
  • Satu nilai galat dikirim naik ke pemanggil. Bila pemanggil pun tidak menangkapnya, Node menghentikan program dan mencetak transkrip galat.
  • Nilai yang dilempar bisa ekspresi apa pun, tetapi bentuk yang disarankan adalah new Error("pesan") — alasannya menyusul di bagian akhir chapter.

Alternatif yang mungkin terpikir adalah mengembalikan nilai penanda dari function — misalnya return -1 atau return null saat input tidak valid. Cara itu memindahkan beban ke pemanggil: setiap pemanggil wajib memeriksa hasil sebelum memakainya, dan satu pemeriksaan yang terlewat membuat angka keliru ikut dipakai seolah sah. throw bekerja sebaliknya: alur berhenti paksa di titik kegagalan, dan pemanggil yang tidak menyiapkan penangkap akan melihat kegagalan itu di transkrip — bukan diam-diam menyimpan nilai yang tidak sah.

Karena throw adalah statement, ia hanya boleh berdiri sendiri sebagai pernyataan, bukan di dalam ekspresi seperti nilai ternary. Pemeriksaan node --check pada baris berikut langsung ditolak parser:

js
const pilih = true ? 1 : throw new Error("tidak boleh");
text
SyntaxError: Unexpected token 'throw'

Guard Clause: Periksa Dulu, Baru Kerjakan #

Pola penempatan throw yang paling sering dipakai bernama guard clause: di awal function, daftar syarat diperiksa satu per satu, dan setiap syarat yang gagal langsung melempar galat. Sisa isi function baru berisi pekerjaan utamanya. Bila pola ini terasa familier, itu karena chapter Control Flow Best Practices sudah menyinggungnya sebagai pseudocode guard clause — kini pernyataan penolaknya berbentuk nyata: throw. Berkas berikut menerapkannya pada penghitung harga diskon:

// validasi-pesanan.js
// Guard clause: periksa syarat lebih dulu, lempar galat bila tidak terpenuhi.

function hitungHargaDiskon(harga, persenDiskon) {
  if (typeof harga !== "number" || harga <= 0) {
    throw new Error("harga harus number lebih besar dari nol");
  }
  if (persenDiskon < 0 || persenDiskon > 100) {
    throw new Error("persenDiskon harus di antara 0 dan 100");
  }
  return harga - (harga * persenDiskon) / 100;
}

console.log(hitungHargaDiskon(25000, 10));
console.log(hitungHargaDiskon(60000, 250));
console.log(hitungHargaDiskon(8000, 0));

Pemanggilan pertama sah, pemanggilan kedua membawa diskon 250 — dan program berhenti di situ. Keluaran yang dihasilkan di terminal (path dipendekkan; baris internal engine diringkas):

text
22500
/home/…/validasi-pesanan.js:9
    throw new Error("persenDiskon harus di antara 0 dan 100");
    ^

Error: persenDiskon harus di antara 0 dan 100
    at hitungHargaDiskon (/home/…/validasi-pesanan.js:9:11)
    at Object.<anonymous> (/home/…/validasi-pesanan.js:15:13)
    ... 7 baris internal engine lainnya ...
Node.js v24.21.0

Mari kita bedah baris-barisnya:

  1. Baris 5–7 (if (typeof harga !== "number" || harga <= 0) { ... }): guard pertama menolak harga yang bukan number atau tidak positif. Guard ini wajib lebih dulu — perhitungan terhadap harga yang salah tipe akan menghasilkan angka yang keliru tanpa terdeteksi.
  2. Baris 8–10 (if (persenDiskon < 0 || persenDiskon > 100) { ... }): guard kedua menegakkan rentang diskon. Kondisi gabungan || dari section Operators menangkap dua arah pelanggaran sekaligus: negatif dan lebih dari 100.
  3. Baris 11 (return harga - (harga * persenDiskon) / 100;): karena semua guard lolos, perhitungan utama boleh berjalan. Pembaca hitungHargaDiskon kini yakin kedua parameter sudah valid — tidak perlu if tambahan di tengah logika.
  4. Baris 14 (console.log(hitungHargaDiskon(25000, 10))): pemanggilan sah menghasilkan 22500, tercetak sebelum masalah muncul.
  5. Baris 15 (console.log(hitungHargaDiskon(60000, 250))): guard kedua terpicu, throw menghentikan function, dan tidak ada nilai yang kembali. Pemanggilan ketiga di baris 16 tidak pernah sampai dieksekusi.

Perhatikan juga transkripnya: pesan yang muncul persis Error: persenDiskon harus di antara 0 dan 100 — teks yang kita tulis sendiri di dalam new Error(...). Pesan galat bukan teks dari engine, melainkan pesan yang kita rancang untuk pembaca transkrip.

Urutan guard juga menyimpan keputusan: syarat paling mendasar diperiksa lebih dulu — tipe data sebelum rentang — dan tiap guard menulis pesannya sendiri. Dengan begitu pembaca transkrip langsung tahu guard mana yang terpicu, tanpa perlu membuka kode sumber. Keuntungan lainnya terasa saat membaca ulang function setelah berbulan-bulan: syarat-syaratnya terbaca sebagai daftar di awal, bukan tersebar sebagai if di tengah logika.

Transkrip di atas sekaligus menunjukkan jalur naiknya galat: frame at hitungHargaDiskon adalah lokasi throw, dan frame at Object.<anonymous> adalah pemanggilnya di baris 15. Galat yang tidak ditangkap menempuh jalur itu terus ke atas sampai Node — jalur penuhnya akan kita baca serius di chapter terakhir section ini.

Apa Saja yang Boleh Dilempar #

throw menerima ekspresi apa pun. throw 42, throw "pesan", bahkan throw { kode: 500 } semuanya sah secara sintaks. Namun kebebasan ini ada harganya. Bandingkan transkrip ketika yang dilempar sebuah string biasa:

// lempar-teks.js
// Demonstrasi: melempar nilai yang bukan Error.

throw "Stok tidak boleh negatif";
text
/home/…/lempar-teks.js:4
throw "Stok tidak boleh negatif";
^
Stok tidak boleh negatif
(Use `node --trace-uncaught ...` to show where the exception was thrown)

Tiga hal hilang pada transkrip di atas. Tidak ada label jenis galat seperti Error: di depan pesan. Tidak ada daftar baris at ... yang menunjukkan dari mana galat datang. Dan di dalam program, nilai yang tertangkap hanyalah string — typeof-nya "string", tanpa properti name, message, maupun stack untuk diselidiki.

Desain pesan galat juga layak direncanakan, bukan ditulis asal. Dua kebiasaan yang membantu:

  • Sebutkan apa yang salah dan apa yang diterima. "persenDiskon harus di antara 0 dan 100" menyebut parameter dan rentangnya; bandingkan dengan "input salah" yang tidak memberi petunjuk apa pun.
  • Sertakan nilai yang terlibat bila membantu. Pesan seperti "Kode kupon tidak dikenal: SALAHKODE" langsung menunjukkan data pemicunya — pola yang kelak dipakai berulang di chapter Custom Errors.

Pesan galat yang baik juga bukan asal teks. Sebut apa yang salah dan nilai apa yang diterima: "persenDiskon harus di antara 0 dan 100" jauh lebih mudah ditindaklanjuti dibanding "input salah".

Satu masalah tersisa dari chapter ini: setiap throw yang tidak ditangkap menghentikan seluruh program. Untuk diskon 250 sekalipun, berhenti total rasanya berlebihan — entri lain masih layak diproses, dan data yang sudah sah tidak seharusnya ikut terbuang. Chapter berikutnya memperkenalkan try-catch, alat untuk menangkap galat agar program tetap melanjutkan sisanya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

lempar-teks.js

Unduh berkas
javascript
// lempar-teks.js
// Demonstrasi: melempar nilai yang bukan Error.

throw "Stok tidak boleh negatif";

validasi-pesanan.js

Unduh berkas
javascript
// validasi-pesanan.js
// Guard clause: periksa syarat lebih dulu, lempar galat bila tidak terpenuhi.

function hitungHargaDiskon(harga, persenDiskon) {
  if (typeof harga !== "number" || harga <= 0) {
    throw new Error("harga harus number lebih besar dari nol");
  }
  if (persenDiskon < 0 || persenDiskon > 100) {
    throw new Error("persenDiskon harus di antara 0 dan 100");
  }
  return harga - (harga * persenDiskon) / 100;
}

console.log(hitungHargaDiskon(25000, 10));
console.log(hitungHargaDiskon(60000, 250));
console.log(hitungHargaDiskon(8000, 0));