Belajar dengan membaca secara berurutan

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
}
Proses Berhenti Hanya Kalau Diberikan ke throw
Hanya new Errordi dalam ifError objectdibuatTidak terjadiapa-apa, lanjut1200 * 0mengembalikan 0throw new Errordi dalam ifError objectdibuatException langsungterjadi di situValue sampai keerror di catch
Kedua cara sama-sama membuat Error object. Hanya kalau diberikan ke throw, prosesnya langsung berhenti di situ.

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.

Cek skor kepuasan sebelum menyimpan survei. scores sudah dideklarasikan.

① Kalau skornya bukan bilangan bulat, lempar exception dengan pesan "Skor kepuasan harus bilangan bulat".

② Kalau di luar 1–5, lempar exception dengan pesan "Skor kepuasan harus antara 1 dan 5".

③ Kalau tidak ada masalah, kembalikan dalam bentuk "Terkirim dengan skor kepuasan 4".

④ Berikan tiap value di scores secara berurutan, tetap lanjut walaupun ada yang gagal, lalu tampilkan hasilnya atau pesan error-nya.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
}
Posisi Jenis Buatan extends Error
Error — jenis exception yang punya message dan name
  • Untuk exception yang dibuat dengan new Error("pesan") maupun semua jenis di dalam kotak ini, instanceof Error bernilai true
Jenis bawaan — sudah disediakan JavaScript sejak awal
  • SyntaxError / TypeError / RangeError — jenis yang dibahas di artikel sebelumnya
Jenis yang dibuat dengan extends Error
  • ValidationError — menandakan kesalahan input
  • name adalah "ValidationError" yang ditulis di dalam class
  • instanceof ValidationError hanya true di dalam kotak ini
Keduanya ada di dalam kotak Error, dan tidak ada yang berada di dalam kotak yang lain. instanceof ValidationError hanya true di dalam kotaknya sendiri.

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 throwname yang terbaca di catchHasil instanceof Error
"Kode pos tidak valid" (string)undefined (string tanpa name)false
new Error("Kode pos tidak valid")Errortrue
ValidationError dengan nameValidationErrortrue
ValidationError tanpa nameError (warisan apa adanya)true

Tampilkan alasan file ditolak. QuotaError, files, dan checkFile sudah dideklarasikan.

① Definisikan FormatError yang mewarisi Error, lalu atur name-nya.

② Di checkFile, lempar FormatError dengan pesan "Hanya file PDF yang diterima" kalau ekstensinya bukan pdf.

③ Cek tiap file di files secara berurutan, lalu tampilkan return value-nya atau "name: message".

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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");
}
Urutan Pengecekan Menentukan Cabangnya
instanceof Errordicek duluantrue walaupunValidationErrorCek berikutnyatidak dicobaTampil "Gagalmenyimpan"ValidationErrordicek duluantrue: dibuat dariValidationErrorCek Errortidak dicobaTampil "Silakanperiksa input"
Di baris atas, pengecekan pertama bernilai true, jadi pengecekan kedua tidak pernah dicoba. Cek jenis yang mewarisi Error sebelum Error itu sendiri.

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.

Proses permintaan perubahan alamat email. ValidationError, changeEmail, dan requests sudah dideklarasikan.

① Berikan tiap item di requests ke changeEmail, lalu tampilkan hasilnya.

② Untuk ValidationError, tampilkan "Periksa alamat email kamu: " diikuti pesannya.

③ Untuk Error lainnya, tampilkan "Gagal mengubah email: " diikuti nama jenisnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
}
Exception Asli Tetap Tersimpan di cause
error — PaymentError yang dilempar readPayment
  • error.name adalah PaymentError
  • error.message adalah Hasil pembayaran tidak bisa dibaca
error.cause — exception asli yang dilempar JSON.parse
  • error.cause.name adalah SyntaxError
  • error.cause.message adalah penjelasan dari JSON.parse, tanpa diubah
Exception luarnya adalah PaymentError, dan cause berisi SyntaxError aslinya. Exception asli tersimpan utuh di error.cause.

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.

Impor data absensi. ImportError dan records sudah dideklarasikan.

① Di importRecords, ambil 5 karakter pertama dari tiap jam masuk lalu tampilkan dalam bentuk "E-104: 09:02".

② Kalau gagal, lempar ImportError dengan pesan "Impor dihentikan di baris E-105", beserta exception aslinya.

③ Panggil importRecords, dan kalau gagal, tampilkan pesannya lalu "Exception asli: " diikuti nama jenis exception aslinya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Apa name dari exception yang dibuat dari class ShippingError extends Error {}?

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?