Kenapa ini penting
Program yang bagus bukan program yang tidak pernah error, melainkan program yang tahu harus berbuat apa saat error terjadi. Input pengguna bisa salah, server bisa mati, file bisa tidak ada, JSON dari API bisa rusak. Tanpa penanganan error, satu kesalahan kecil membuat seluruh aplikasi berhenti, layar putih, dan pengguna bingung.
Bayangkan kamu kasir di toko. Ketika mesin EDC gagal, kamu tidak meninggalkan toko. Kamu menawarkan pembayaran tunai, mencatat kejadiannya, lalu melanjutkan pelayanan. Itulah try/catch: mencoba, menangkap masalah, dan membereskan (finally). Di modul ini kamu juga belajar melempar error sendiri dengan pesan yang jelas, dan membuat custom error class supaya jenis kesalahan bisa dibedakan (misalnya "saldo tidak cukup" berbeda penanganannya dengan "koneksi terputus").
Konsep inti
try, catch, finally
try {
// kode yang mungkin gagal
} catch (error) {
// dijalankan HANYA jika try melempar error
} finally {
// SELALU dijalankan, error atau tidak (bahkan setelah return)
}
- Begitu error dilempar, sisa baris di
trydilewati, langsung lompat kecatch. finallycocok untuk membereskan: menutup koneksi, mematikan indikator loading, melepas kunci.- Sejak ES2019 kamu boleh menulis
catch { ... }tanpa variabel jika tidak membutuhkannya.
throw
throw menghentikan eksekusi function saat ini dan "melempar" nilai ke atas sampai ada catch yang menangkap. Jika tidak ada sama sekali, program berhenti (di Node.js) atau muncul Uncaught Error di console (browser).
Kamu bisa melempar apa saja (throw "gagal"), tetapi selalu lempar object Error, karena hanya Error yang membawa stack (jejak lokasi kejadian) dan bisa dibedakan dengan instanceof.
Objek Error dan jenis bawaan
Setiap error punya:
name: jenis error, misalnya"TypeError".message: penjelasan untuk manusia.stack: jejak panggilan function tempat error terjadi.cause(ES2022): error asli penyebabnya,new Error("pesan", { cause: errAsli }).
| Jenis | Kapan muncul | Contoh |
|---|---|---|
Error |
Umum | throw new Error("...") |
TypeError |
Operasi pada tipe yang salah | undefined.nama, memanggil bukan function |
ReferenceError |
Variabel tidak ada | console.log(xyz) |
RangeError |
Angka di luar rentang | new Array(-1), toFixed(200) |
SyntaxError |
Kode/JSON tidak valid | JSON.parse("{rusak") |
Custom error class
Dengan class ... extends Error, kamu membuat jenis error sendiri yang membawa data tambahan:
class ValidasiError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidasiError";
this.field = field;
}
}
Lalu di catch, bedakan dengan instanceof dan tangani sesuai jenisnya. Error yang tidak kamu kenali sebaiknya dilempar ulang (throw err), jangan ditelan diam-diam.
Kapan memakai try/catch?
Bukan di setiap baris. Tangkap error di tempat kamu bisa melakukan sesuatu terhadapnya: menampilkan pesan ke pengguna, mencoba ulang, memakai nilai cadangan. Validasi yang bisa dicek dengan if (misalnya "apakah input kosong?") lebih baik dicek langsung sebelum menjadi error.
Contoh kode
// 1. try/catch/finally dasar
function parseKonfigurasi(teks) {
try {
const cfg = JSON.parse(teks);
console.log("Konfigurasi terbaca:", cfg.tema);
return cfg;
} catch (err) {
console.log(`Gagal membaca (${err.name}): pakai default`);
return { tema: "terang" };
} finally {
console.log("Selesai memproses konfigurasi");
}
}
parseKonfigurasi('{"tema":"gelap"}');
// → Konfigurasi terbaca: gelap
// → Selesai memproses konfigurasi
parseKonfigurasi("{rusak");
// → Gagal membaca (SyntaxError): pakai default
// → Selesai memproses konfigurasi
// 2. Custom error class dengan data tambahan
class AppError extends Error {
constructor(message, options) {
super(message, options); // options bisa berisi { cause }
this.name = this.constructor.name; // otomatis sesuai nama class turunan
}
}
class ValidasiError extends AppError {
constructor(field, message) {
super(message);
this.field = field;
}
}
class SaldoTidakCukupError extends AppError {
constructor(saldo, diminta) {
super(`Saldo Rp${saldo} tidak cukup untuk Rp${diminta}`);
this.kekurangan = diminta - saldo;
}
}
// 3. Melempar error dengan pesan jelas
function transfer(saldo, jumlah, tujuan) {
if (!tujuan) throw new ValidasiError("tujuan", "Rekening tujuan wajib diisi");
if (!Number.isFinite(jumlah) || jumlah <= 0) {
throw new ValidasiError("jumlah", "Jumlah harus angka positif");
}
if (jumlah > saldo) throw new SaldoTidakCukupError(saldo, jumlah);
return saldo - jumlah;
}
// 4. Menangani per jenis error
function prosesTransfer(saldo, jumlah, tujuan) {
try {
const sisa = transfer(saldo, jumlah, tujuan);
return `Berhasil. Sisa saldo Rp${sisa}`;
} catch (err) {
if (err instanceof ValidasiError) return `Input salah di "${err.field}": ${err.message}`;
if (err instanceof SaldoTidakCukupError) return `${err.message} (kurang Rp${err.kekurangan})`;
throw err; // error tak dikenal: jangan ditelan!
}
}
console.log(prosesTransfer(100000, 50000, "123")); // → Berhasil. Sisa saldo Rp50000
console.log(prosesTransfer(100000, -5, "123")); // → Input salah di "jumlah": Jumlah harus angka positif
console.log(prosesTransfer(100000, 150000, "123")); // → Saldo Rp100000 tidak cukup untuk Rp150000 (kurang Rp50000)
console.log(prosesTransfer(100000, 1000, "")); // → Input salah di "tujuan": Rekening tujuan wajib diisi
// 5. Properti error
try {
transfer(0, 10, "x");
} catch (e) {
console.log(e.name, "|", e instanceof AppError, "|", e instanceof Error);
// → SaldoTidakCukupError | true | true
}
// 6. Membungkus error dengan cause (menjaga jejak aslinya)
function muatProfil(json) {
try {
return JSON.parse(json);
} catch (err) {
throw new AppError("Profil pengguna rusak", { cause: err });
}
}
try {
muatProfil("{nama:");
} catch (e) {
console.log(e.message, "← penyebab:", e.cause.name); // → Profil pengguna rusak ← penyebab: SyntaxError
}
// 7. finally tetap jalan meski ada return
function cobaFinally() {
try {
return "dari try";
} finally {
console.log("finally tetap jalan");
}
}
console.log(cobaFinally());
// → finally tetap jalan
// → dari try
// 8. Pola "hasil atau error" tanpa melempar (untuk validasi massal)
function validasiTransaksi(t) {
const masalah = [];
if (!t.keterangan) masalah.push("keterangan kosong");
if (typeof t.jumlah !== "number") masalah.push("jumlah bukan angka");
return masalah.length ? { ok: false, masalah } : { ok: true };
}
console.log(validasiTransaksi({ keterangan: "", jumlah: "10" }));
// → { ok: false, masalah: [ 'keterangan kosong', 'jumlah bukan angka' ] }
Perhatikan trik this.name = this.constructor.name di AppError. Semua turunannya otomatis punya name yang benar tanpa perlu menulis ulang. Pesan error yang baik menjawab tiga hal: apa yang salah, di mana, dan kalau bisa bagaimana memperbaikinya.
Kesalahan umum
- Menelan error diam-diam.
catch (e) {}kosong menyembunyikan bug. Minimal catat (console.error(e)), atau lempar ulang error yang tidak kamu kenali. - Melempar string.
throw "gagal"tidak punyastackdan tidak bisa dicek denganinstanceof. Selaluthrow new Error("gagal"). - Lupa
super(message)di custom error. Tanpa itu,messagekosong danthistidak bisa dipakai. - Mengira try/catch menangkap error di callback timer.
try { setTimeout(() => { throw ... }) } catch {}tidak menangkap apa-apa, karena callback berjalan nanti setelah blok try selesai. Pasang try/catch di dalam callback (atau pakai Promise/async, dibahas berikutnya). - Membungkus seluruh program dalam satu try/catch raksasa. Kamu kehilangan konteks. Tangkap di level yang bisa memberi respons berarti.
Latihan
- Mudah, Tulis
bagi(a, b)yang melemparRangeErrorjikabbernilai 0. Panggil di dalam try/catch dan cetak pesannya. - Sedang, Buat
TidakDitemukanError extends Errordengan propertiid. TuliscariProduk(daftar, id)yang melemparnya jika produk tidak ada. - Menantang, Tulis
parseAngkaRupiah(teks)yang mengubah"Rp1.250.000"menjadi1250000. LemparValidasiError(dengancausejika ada error dalam) untuk input seperti"Rp1,2jt". Uji tiga input.
Lihat solusi
// 1
function bagi(a, b) {
if (b === 0) throw new RangeError("Pembagi tidak boleh nol");
return a / b;
}
try { bagi(10, 0); } catch (e) { console.log(e.name, e.message); } // → RangeError Pembagi tidak boleh nol
// 2
class TidakDitemukanError extends Error {
constructor(id) {
super(`Produk dengan id ${id} tidak ditemukan`);
this.name = "TidakDitemukanError";
this.id = id;
}
}
function cariProduk(daftar, id) {
const p = daftar.find((x) => x.id === id);
if (!p) throw new TidakDitemukanError(id);
return p;
}
try { cariProduk([{ id: 1, nama: "Teh" }], 7); }
catch (e) { if (e instanceof TidakDitemukanError) console.log(e.message); } // → Produk dengan id 7 tidak ditemukan
// 3
class ValidasiError extends Error {
constructor(message, options) { super(message, options); this.name = "ValidasiError"; }
}
function parseAngkaRupiah(teks) {
const bersih = String(teks).replace(/^Rp\s?/, "").replaceAll(".", "");
if (!/^\d+$/.test(bersih)) throw new ValidasiError(`Format rupiah tidak valid: "${teks}"`);
return Number(bersih);
}
for (const input of ["Rp1.250.000", "Rp 75.000", "Rp1,2jt"]) {
try { console.log(parseAngkaRupiah(input)); }
catch (e) { console.log(`${e.name}: ${e.message}`); }
}
// → 1250000
// → 75000
// → ValidasiError: Format rupiah tidak valid: "Rp1,2jt"
Ringkasan
trymencoba,catchmenangkap,finallyselalu jalan (bahkan setelahreturn).- Selalu lempar object
Error(atau turunannya) agar punyaname,message,stack. - Custom error class (
extends Error) membuat kesalahan bisa dibedakan denganinstanceof. - Gunakan
{ cause }untuk membungkus error tanpa kehilangan penyebab aslinya. - Jangan telan error; tangani yang kamu kenali, lempar ulang sisanya.