Soal 1Apa name dari exception yang dibuat dari class ShippingError extends Error {}?
Melempar Exception — throw dan Custom Error
Lempar exception dengan throw new Error, buat jenis error sendiri dengan extends Error, dan simpan error aslinya lewat cause.
Walaupun halaman order mengirim jumlah 0, function yang menghitung subtotal tetap saja menghitungnya, dan order senilai 0 rupiah pun lolos. Sekadar menampilkan peringatan juga tidak menghentikan pemanggil: pemanggil tidak sadar ada masalah dan lanjut menyimpan order-nya.
Artikel ini membahas throw new Error untuk melempar exception sendiri, dan custom error, yaitu jenis error yang kamu definisikan sendiri.
Menghentikan Proses Saat Value Tidak Valid — throw new Error
Misalnya kamu tidak mau perhitungan subtotal dijalankan kalau jumlahnya berupa desimal atau angka negatif. Memberi tanda dengan return null saja tidak cukup: kalau pemanggil lupa mengeceknya, penambahan ongkos kirim null + 500 menghasilkan 500, dan perhitungannya terus berjalan.
Error object (value yang punya name dan message, khusus untuk dilempar sebagai exception) dibuat dengan new Error("pesan"). Kalau value itu kamu berikan ke throw, exception langsung terjadi di situ; kalau pemanggilannya ada di dalam try, value itu sampai sebagai error di catch.
function calcSubtotal(unitPrice, quantity) {
// Kalau bukan bilangan bulat 1 atau lebih, lempar exception di sini
if (!Number.isInteger(quantity) || quantity < 1) {
// new Error saja tidak menghentikan apa pun. Berikan ke throw agar jadi exception
throw new Error(`Jumlah harus bilangan bulat 1 atau lebih: ${quantity}`);
}
return unitPrice * quantity;
}
try {
console.log(calcSubtotal(1200, 3)); // 3600
console.log(calcSubtotal(1200, 0)); // melempar exception, jadi tidak ada yang ditampilkan
console.log("Order dikonfirmasi"); // baris ini juga tidak dijalankan
} catch (error) {
console.log(error.name); // Error
console.log(error.message); // Jumlah harus bilangan bulat 1 atau lebih: 0
}
Taruh if untuk validasi di bagian awal function, sebelum perhitungan. Sama seperti return, throw langsung keluar dari function saat itu juga, jadi return unitPrice * quantity; setelahnya tidak dijalankan dan pemanggil tidak pernah menerima 0.
Membuat Jenis untuk Kesalahan Input — extends Error
Exception yang dilempar dengan new Error selalu punya name berupa Error, apa pun isi validasinya. Kode pos dengan jumlah digit yang salah dan stok yang habis sampai ke catch dengan nama yang sama, jadi catch tidak bisa membedakan apakah kegagalan itu bisa diatasi pengguna dengan memperbaiki input-nya.
Kalau kamu menambahkan extends Error ke class (syntax untuk mendefinisikan jenis object yang dibuat dengan new), kamu mendapat jenis baru yang mewarisi perilaku Error. Tulis name = "ValidationError"; di dalam kurung kurawal, dan exception yang dibuat dari jenis ini akan punya string itu sebagai name.
// Mewarisi Error untuk membuat jenis khusus kesalahan input
class ValidationError extends Error {
name = "ValidationError"; // menjadi name dari exception yang dibuat dengan new
}
function checkZipCode(zipCode) {
if (zipCode.length !== 5) {
throw new ValidationError(`Masukkan kode pos 5 digit: ${zipCode}`);
}
return zipCode;
}
try {
checkZipCode("4011");
} catch (error) {
console.log(error.name); // ValidationError
console.log(error.message); // Masukkan kode pos 5 digit: 4011
console.log(error instanceof Error); // true
}
- Untuk exception yang dibuat dengan
new Error("pesan")maupun semua jenis di dalam kotak ini,instanceof Errorbernilaitrue
SyntaxError/TypeError/RangeError— jenis yang dibahas di artikel sebelumnya
ValidationError— menandakan kesalahan inputnameadalah"ValidationError"yang ditulis di dalam class- instanceof ValidationError hanya true di dalam kotak ini
Kamu juga bisa memberikan string ke throw, tapi string tidak punya name maupun message, jadi membaca error.message di catch menghasilkan undefined. Tabel di bawah menunjukkan apa yang bisa dibaca di catch untuk setiap jenis value yang diberikan ke throw.
| Value yang diberikan ke throw | name yang terbaca di catch | Hasil instanceof Error |
|---|---|---|
| "Kode pos tidak valid" (string) | undefined (string tanpa name) | false |
| new Error("Kode pos tidak valid") | Error | true |
| ValidationError dengan name | ValidationError | true |
| ValidationError tanpa name | Error (warisan apa adanya) | true |
Cek Jenis Buatanmu Lebih Dulu — Urutan instanceof
Kesalahan input bisa diperbaiki pengguna, tapi exception tak terduga seperti TypeError tidak akan hilang walaupun input-nya diubah. Kamu ingin catch menampilkan dua pesan yang berbeda, tapi kalau urutan pengecekannya salah, kesalahan input pun ikut mendapat pesan yang dimaksudkan untuk exception tak terduga.
if dan else if dicek dari atas ke bawah, dan hanya cabang pertama yang cocok yang dijalankan. ValidationError juga bernilai true untuk instanceof Error, jadi kalau instanceof Error ditulis lebih dulu, ValidationError juga akan masuk ke cabang itu.
class ValidationError extends Error {
name = "ValidationError";
}
const error = new ValidationError("Masukkan kode pos 5 digit");
// Kalau Error dicek lebih dulu, ValidationError juga masuk ke cabang ini
if (error instanceof Error) {
console.log("Gagal menyimpan"); // Gagal menyimpan
} else if (error instanceof ValidationError) {
console.log("Silakan periksa input kamu"); // tidak sampai ke sini
}
// Cek jenis turunannya lebih dulu
if (error instanceof ValidationError) {
console.log("Silakan periksa input kamu"); // Silakan periksa input kamu
} else if (error instanceof Error) {
console.log("Gagal menyimpan");
}
Jenis bawaan seperti TypeError juga mewarisi Error, jadi cabang instanceof Error yang ditaruh paling akhir akan menangkap semuanya. Pakai cabang itu untuk menampilkan pesan lain bagi exception yang tidak bisa diatasi dengan memperbaiki input.
Melempar Beserta Exception Aslinya — cause dan Uncaught Exception
Misalnya SyntaxError terjadi di function yang membaca respons dari layanan pembayaran. Kalau kamu hanya melemparnya ulang, jenisnya tidak memberi tahu pemanggil bahwa kegagalan itu terjadi saat memproses pembayaran. Tapi kalau kamu menggantinya dengan exception jenis baru, informasi bahwa awalnya itu SyntaxError jadi hilang.
cause (opsi untuk menyertakan exception asli ke exception baru) ditulis di argument ke-2, seperti new Error("pesan", { cause: error }). Cara ini juga berlaku untuk jenis yang dibuat dengan extends Error, dan pihak yang menangkap exception baru itu bisa membaca exception aslinya dari error.cause.
class PaymentError extends Error {
name = "PaymentError";
}
function readPayment(text) {
try {
return JSON.parse(text);
} catch (error) {
// Sertakan exception asli sebagai cause, lalu lempar sebagai error pembayaran
throw new PaymentError("Hasil pembayaran tidak bisa dibaca", { cause: error });
}
}
try {
readPayment('{"orderId":"P-3107","amount":}');
} catch (error) {
console.log(error.name); // PaymentError
console.log(error.message); // Hasil pembayaran tidak bisa dibaca
console.log(error.cause.name); // SyntaxError
}
error.nameadalahPaymentErrorerror.messageadalahHasil pembayaran tidak bisa dibaca
error.cause.nameadalahSyntaxErrorerror.cause.messageadalah penjelasan dari JSON.parse, tanpa diubah
Kalau kamu mengganti exception tanpa cause, error.cause bernilai undefined, dan jenis maupun penjelasan aslinya tidak bisa dibaca lagi. Dengan cause, pemanggil bisa memakai error.name untuk menampilkan pesan kegagalan pembayaran, sambil tetap mencatat isi error.cause ke log.
Exception yang Tidak Ditangkap Menghentikan Eksekusi
Exception yang dilempar tapi tidak diterima catch mana pun disebut uncaught exception. Eksekusi berhenti di situ, dan tidak ada satu pun baris sisanya yang dijalankan. Kalau try bagian luar di kode sebelumnya kamu hapus, error dengan nama jenis dan pesannya akan tampil, seperti PaymentError: Hasil pembayaran tidak bisa dibaca.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Kalau instanceof Error dicek lebih dulu, exception ValidationError masuk ke cabang mana?
Soal 3Kamu menangkap exception yang dilempar beserta cause dengan catch (error). Bagaimana cara membaca name dari exception aslinya?