Mengapa Program Tidak Boleh "Menelan Galat Diam-Diam"?
Kesalahan jaringan, masukan JSON yang rusak, atau penyimpanan peramban yang penuh adalah kenyataan sehari-hari di lingkungan produksi. Dua kesalahan paling berbahaya dalam menangani galat adalah:
- Tidak menangani galat sama sekali pada operasi berisiko (seperti
JSON.parse()), sehingga satu data yang rusak menghentikan seluruh eksekusi skrip di halaman pengguna. - **Menelan galat secara diam-diam (silent catch)** dengan menulis
try { ... } catch (e) {}kosong tanpa pencatatan atau pemulihan, sehingga ketika fitur gagal bekerja, tidak ada seorang pun yang tahu apa penyebabnya!
1. Mekanisme try...catch...finally dan throw
function bacaKonfigurasiJson(teksMentah) {
try {
const data = JSON.parse(teksMentah);
if (!data || typeof data.port !== "number") {
// Melempar galat secara sengaja jika struktur data tidak memenuhi syarat
throw new TypeError("Properti 'port' wajib berupa angka.");
}
return data;
} catch (galat) {
// Blok catch menangkap galat dari JSON.parse() maupun dari throw di atas
console.error("Gagal memuat konfigurasi, menggunakan nilai bawaan:", galat.message);
return { port: 8080, mode: "aman" };
} finally {
// Blok finally SELALU dijalankan di akhir (baik sukses maupun gagal,
// bahkan jika di dalam try/catch terdapat perintah return!)
console.log("Pemeriksaan konfigurasi selesai.");
}
}
Error, Jangan Melempar String Mentah!Di JavaScript, secara teknis Anda bisa menulis throw "Gagal login";. Namun jangan pernah melakukan itu! Jika Anda melempar string mentah, objek yang ditangkap di catch (err) tidak memiliki properti err.message, err.name, dan yang paling penting tidak memiliki jejak tumpukan pemanggilan (**err.stack / stack trace) untuk mencari di baris mana kesalahan terjadi. Selalu gunakan throw new Error("Pesan jelas")** atau subkelasnya!
2. Mengenal Jenis-Jenis Error Bawaan JavaScript
Setiap objek galat standar memiliki properti name dan message:
| Kelas Galat Bawaan | Kapan Terjadi atau Digunakan? |
|---|---|
SyntaxError | Kode tidak valid secara tata bahasa, atau ketika JSON.parse() menerima teks JSON yang rusak. |
ReferenceError | Mencoba mengakses variabel yang belum dideklarasikan atau masih berada di Temporal Dead Zone. |
TypeError | Nilai bukanlah tipe yang diharapkan (misalnya memanggil null.toString() atau mengubah variabel const). |
RangeError | Nilai numerik berada di luar rentang yang diizinkan (misalnya new Array(-5) atau (10).toFixed(200)). |
URIError | Fungsi pengodean URL seperti decodeURIComponent("%") menerima urutan persen yang cacat. |
AggregateError | Membungkus beberapa galat sekaligus dalam satu objek (misalnya saat Promise.any() gagal seluruhnya). |
3. Membuat Custom Error Class dan Merangkai Penyebab dengan { cause }
Dalam aplikasi yang terstruktur, bagian tampilan (UI layer) perlu membedakan apakah kegagalan terjadi karena kesalahan input pengguna (GalatValidasi), kesalahan jaringan/server (GalatJaringan), atau bug pemrograman (TypeError).
Kita dapat membuat kelas galat khusus dengan mewarisi Error, serta meneruskan galat asli tingkat bawah menggunakan opsi { cause: galatAsli } (status Baseline Widely Available sejak ES2022):
class GalatLayananData extends Error {
constructor(pesan, { kodeStatus = 500, cause } = {}) {
// Meneruskan pesan dan opsi { cause } ke konstruktor Error bawaan
super(pesan, { cause });
this.name = "GalatLayananData";
this.kodeStatus = kodeStatus;
}
}
class GalatValidasiInput extends Error {
constructor(kolom, pesan) {
super(`Kolom '${kolom}': ${pesan}`);
this.name = "GalatValidasiInput";
this.kolom = kolom;
}
}
// Contoh menangkap galat tingkat rendah, membungkusnya dengan konteks bisnis yang jelas,
// namun tetap mempertahankan jejak galat aslinya lewat 'cause':
function ambilProfilDariCache(teksJson) {
try {
return JSON.parse(teksJson);
} catch (galatAsli) {
throw new GalatLayananData(
"Data profil di penyimpanan lokal rusak dan tidak dapat diurai.",
{ kodeStatus: 422, cause: galatAsli }
);
}
}
// Menangani galat secara selektif dengan instanceof (dan melempar ulang galat yang tidak dikenal!)
try {
ambilProfilDariCache("{ json_rusak ");
} catch (err) {
if (err instanceof GalatLayananData) {
console.warn(`[Status ${err.kodeStatus}] ${err.message}`);
console.warn("Penyebab teknis asli:", err.cause.message);
} else {
// Jangan menelan bug tak terduga! Lempar ulang jika bukan galat yang kita antisipasi.
throw err;
}
}
Kesalahan Umum dan Cara Menghindarinya
- Menangkap semua galat di
catchdan menganggap semuanya adalah satu masalah yang sama:- Buruk: Menulis blok
trysepanjang 50 baris lalu di dalamcatchlangsung menampilkan "Koneksi internet Anda terputus", padahal yang terjadi di dalamtryadalahTypeErrorakibat salah ketik nama variabel! - Lebih Baik: Jaga agar blok
trysekecil dan sespesifik mungkin hanya di sekitar operasi yang memang berisiko gagal, dan periksa tipe galat menggunakaninstanceofdi dalamcatch.
- Buruk: Menulis blok
- Menggunakan
returndi dalam blokfinally:- Bahaya: Jika Anda menulis pernyataan
return nilaiLaindi dalam blokfinally, pernyataan tersebut akan menimpa nilaireturndari bloktrydan bahkan membatalkanthrowgalat dari blokcatch! Gunakanfinallymurni hanya untuk pembersihan sumber daya (seperti mematikan indikator loading atau menutup koneksi).
- Bahaya: Jika Anda menulis pernyataan
Latihan Praktik: Validator Data Transaksi dengan Custom Error
Buatlah berkas validasi-transaksi.js dan jalankan dengan Node.js:
- Buat
class GalatTransaksi extends Erroryang menerima(pesan, { kode, cause } = {}). - Buat fungsi
prosesPembayaran(jsonInput)yang menguraijsonInputdenganJSON.parse()(jika gagal, lemparGalatTransaksidengankode: "JSON_RUSAK"dancauseberisi galatSyntaxErrorasli). - Jika
nominal <= 0, lemparGalatTransaksidengankode: "NOMINAL_TIDAK_SAH". - Tangkap galat di pemanggil utama dan cetak
err.kode,err.message, sertaerr.cause?.message.