Penanganan error

try/catch/finally, kelas Error kustom, dan jalur error asinkron.

Daftar isi

Program JavaScript yang berjalan nyaris pasti menemui error, baik karena data dari luar tidak sesuai harapan maupun karena kode menyentuh nilai yang tidak ada. Topik ini menata cara menghadapinya, mulai dari try/catch/finally yang menampung error, jenis-jenis Error bawaan, kelas Error kustom untuk kesalahan yang punya nama sendiri, sampai dua jalur penanganan error pada kode asinkron. Setelah membacanya, pola penanganan yang rapi dapat dipakai ulang di mana saja, alih-alih membiarkan program berhenti dengan pesan yang tidak dijelaskan.

Alur try, catch, dan finally#

Blok try membungkus kode yang berpotensi gagal, catch menampung error yang dilempar dari blok try itu, dan finally dijalankan setelah keduanya, baik saat error tertangkap maupun tidak. Kata kunci throw melontarkan error, dan nilai yang dilontarkan sebaiknya selalu object turunan Error karena object itu membawa informasi standar, yaitu name, message, dan stack. Ketika throw dijalankan, eksekusi blok try berhenti dan langsung melompat ke catch terdekat.

js Contoh: catch dan finally pada pembacaan berkas
function bacaFile(nama) {
  try {
    if (nama === "kosong") {
      throw new Error("berkas kosong");
    }
    return "isi berkas";
  } catch (error) {
    console.log("ditangkap: " + error.message);
    return "isi cadangan";
  } finally {
    console.log("finally tetap berjalan");
  }
}

console.log(bacaFile("kosong"));
console.log(bacaFile("normal"));
text Keluaran
ditangkap: berkas kosong
finally tetap berjalan
isi cadangan
finally tetap berjalan
isi berkas

Keluaran di atas memperlihatkan sifat finally yang paling sering dimanfaatkan, yaitu blok ini tetap berjalan meskipun catch sudah mengembalikan nilai. Blok finally pun dipakai untuk pekerjaan pembersihan yang tidak boleh terlewat, misalnya menutup koneksi atau menghentikan pengukuran waktu. Perlu dicatat bahwa variabel error pada catch (error) hanya hidup di dalam bloknya, sehingga dua blok catch yang berbeda boleh memakai nama yang sama. Properti error.stack juga tersedia bila ingin menelusuri urutan pemanggilan fungsi yang berujung pada error, meskipun isinya memuat jalur berkas yang panjang.

Jenis Error bawaan#

JavaScript menyediakan beberapa jenis Error bawaan sehingga jenis kesalahan dapat dibaca langsung dari properti name tanpa menebak dari pesannya. Empat jenis yang paling sering muncul pada program harian dirangkum di tabel berikut.

JenisPemicu umumContoh pesan
TypeErrorMengakses properti pada null atau undefined, atau memanggil nilai yang bukan fungsiCannot read properties of null
ReferenceErrorMemakai nama variabel yang tidak pernah dideklarasikan... is not defined
RangeErrorNilai berada di luar rentang yang sahInvalid array length
SyntaxErrorTeks yang diurai tidak membentuk kode yang sahExpected property name
js Contoh: memancing empat jenis Error bawaan
function uji(tugas) {
  try {
    tugas();
  } catch (error) {
    console.log(error.name + ": " + error.message);
  }
}

uji(() => null.halo);
uji(() => variabelYangTidakAda);
uji(() => new Array(-1));
uji(() => JSON.parse("{salah}"));
text Keluaran
TypeError: Cannot read properties of null (reading 'halo')
ReferenceError: variabelYangTidakAda is not defined
RangeError: Invalid array length
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)

Setiap baris keluaran menampilkan jenis error dan pesannya berdampingan, sehingga sumber masalah terlihat tanpa membaca stack trace, yaitu daftar urutan pemanggilan fungsi saat error dilontarkan. Satu batasan penting menyertai SyntaxError pada tabel di atas. Error pada contoh muncul ketika program sudah berjalan, karena JSON.parse mengurai teksnya saat itu, sehingga try/catch sempat menangkapnya. Kesalahan sintaks pada kode program sendiri terjadi lebih awal, yaitu ketika berkas sedang diurai, sehingga seluruh berkas gagal dijalankan sebelum blok try mana pun sempat dieksekusi.

Kelas Error kustom#

Kelas Error kustom lahir dari extends Error, sehingga jenis kesalahan tertentu dapat dikenali dari kelasnya, bukan dari isi pesannya. Konstruktor kelas kustom memanggil super(pesan) agar properti standar terisi, lalu menetapkan this.name supaya jenisnya tidak lagi tertulis “Error”, dan bebas menambahkan properti lain yang dibutuhkan, misalnya nama kolom yang gagal divalidasi. Berkas berikut memuat demo lengkapnya dan dapat dijalankan langsung dengan node validasi.js.

js validasi.js
// Kelas Error kustom untuk kesalahan validasi form.
class ValidationError extends Error {
  constructor(kolom, pesan) {
    super(pesan);
    this.name = "ValidationError";
    this.kolom = kolom;
  }
}

function validasi(form) {
  if (!form.nama) {
    throw new ValidationError("nama", "kolom nama wajib diisi");
  }
  if (form.umur < 0 || form.umur > 120) {
    throw new ValidationError("umur", "umur harus di antara 0 dan 120");
  }
  return true;
}

function periksa(form) {
  try {
    validasi(form);
    console.log("form valid: " + form.nama);
  } catch (error) {
    if (error instanceof ValidationError) {
      console.log("validasi gagal (" + error.kolom + "): " + error.message);
    } else {
      throw error;
    }
  }
}

periksa({ nama: "Citra", umur: 17 });
periksa({ nama: "", umur: 17 });
periksa({ nama: "Budi", umur: 500 });
text Keluaran
form valid: Citra
validasi gagal (nama): kolom nama wajib diisi
validasi gagal (umur): umur harus di antara 0 dan 120

Pola instanceof pada kode di atas memisahkan dua jenis error yang berbeda penanganannya. ValidationError adalah keluaran yang diharapkan dari proses validasi, sehingga cukup dicetak ringkas untuk pengguna. Error lain yang bukan ValidationError dilempar lagi dengan throw error, karena kesalahan itu kemungkinan besar adalah bug, dan menyembunyikannya justru memindahkan masalah ke tempat yang lebih sulit ditelusuri.

Dua jalur error asinkron#

Error pada kode asinkron tidak bisa ditangkap try/catch biasa yang ditulis di luar, karena hasilnya baru muncul pada waktu lain setelah blok try selesai dijalankan. Kegagalan promise disebut rejection, dan rejection punya dua jalur penanganan yang setara, yaitu .catch di ujung rantai then atau try/catch yang mengelilingi await. Keduanya menerima object error yang sama persis, sehingga pilihannya menjadi soal gaya kode yang sedang ditulis.

js Contoh: rejection ditangkap lewat dua jalur
function ambilRapor(siswa) {
  return new Promise((resolve, reject) => {
    if (siswa === "Budi") {
      reject(new RangeError("nilai siswa Budi belum tercatat"));
      return;
    }
    resolve({ nama: siswa, nilai: 90 });
  });
}

async function jalankan() {
  // jalur pertama: catch pada rantai then
  await ambilRapor("Budi")
    .then((rapor) => console.log("nilai: " + rapor.nilai))
    .catch((error) => console.log("jalur then: " + error.name + " - " + error.message));

  // jalur kedua: try/catch pada await
  try {
    const rapor = await ambilRapor("Budi");
    console.log("nilai: " + rapor.nilai);
  } catch (error) {
    console.log("jalur await: " + error.name + " - " + error.message);
  }
}

jalankan();
text Keluaran
jalur then: RangeError - nilai siswa Budi belum tercatat
jalur await: RangeError - nilai siswa Budi belum tercatat

Diagram berikut merangkum dua jalur itu beserta jalur ketiga yang justru paling berbahaya, yaitu rejection yang tidak ditangani siapa pun.

promiseditolak.catch()await + try/catchcatch pada rantai thentry/catch di sekitar awaittanpapenanganan
Dua jalur penanganan rejection yang setara dan akibat rejection yang dibiarkan

Rejection yang dibiarkan bukan peringatan yang bisa diabaikan. Sejak Node.js 15, unhandled rejection menghentikan proses Node secara langsung, sedangkan pada browser muncul pesan “Uncaught (in promise)” di konsol. Di sisi lain, satu .catch di ujung rantai menampung error dari seluruh mata rantai di depannya, termasuk error yang dilontarkan dari dalam callback then itu sendiri, sehingga rantai yang panjang pun cukup ditutup satu kali.

Ringkasan#

  • Blok try menampung kode yang berpotensi gagal, catch menerima object errornya, dan finally selalu dijalankan sehingga cocok untuk pekerjaan pembersihan.
  • Jenis Error bawaan seperti TypeError, ReferenceError, RangeError, dan SyntaxError menyebut jenis kesalahan lewat properti name.
  • Kelas Error kustom dari extends Error membawa properti tambahan dan dikenali lewat instanceof, sedangkan error yang tidak dikenali dilempar lagi.
  • Pada kode asinkron, rejection ditangkap .catch di ujung rantai then atau try/catch di sekitar await, dan rejection tanpa penanganan menghentikan proses Node sejak Node.js 15.

Referensi#