Chapter sebelumnya selalu berakhir sama: throw yang tidak ditangkap menghentikan seluruh program. Untuk satu entri konfigurasi yang rusak, program pengolah data berhenti total — padahal entri lain masih layak diproses. Perilaku seperti ini tidak dapat kita terima pada aplikasi nyata, di mana input berasal dari pengguna, berkas, atau jaringan dan kapan pun bisa saja tidak sempurna.

try-catch adalah alat untuk itu: kita menandai blok kode yang berisiko, menyediakan penangkapnya, dan memutuskan apa yang dilakukan setelah galat muncul. Blok finally melengkapi pasangan itu dengan jaminan pembersihan. Chapter ini membahas ketiganya, termasuk dua perilaku halus: cakupan variabel error dan urutan return dengan finally.

Menangkap Galat dengan try dan catch #

Aturannya: kode yang berisiko melempar galat ditulis di dalam blok try. Bila galat terlempar di dalamnya — oleh baris kita sendiri maupun oleh function yang dipanggil — eksekusi langsung lompat ke blok catch, dan program melanjutkan dari sana. Bila blok try selesai tanpa galat, blok catch dilewati.

Berkas berikut mem-parsing tiga string JSON. Yang kedua sengaja tidak sah, tetapi program tidak berhenti karena galatnya tertangkap:

// baca-konfigurasi.js
// try-catch: galat ditangkap, sisa program tetap berjalan.

const pengaturan = [
  '{ "tema": "gelap", "ukuran": 14 }',
  '{ tema: gelap }',
  '{ "bahasa": "id" }',
];

for (const teks of pengaturan) {
  try {
    const data = JSON.parse(teks);
    console.log("OK - terbaca: " + JSON.stringify(data));
  } catch (error) {
    console.log("Gagal - " + error.name + ": " + error.message);
  }
}

console.log("Program selesai dengan normal");

Output yang Dihasilkan:

text
OK - terbaca: {"tema":"gelap","ukuran":14}
Gagal - SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)
OK - terbaca: {"bahasa":"id"}
Program selesai dengan normal

Mari kita telaah baris-baris pentingnya:

  1. Baris 11 (try {): blok mulai ditandai. Engine menyiapkan penangkap untuk galat apa pun yang terlempar di dalam blok ini.
  2. Baris 12 (const data = JSON.parse(teks);): JSON.parse melempar SyntaxError ketika stringnya bukan JSON sah. Tanpa try, galat ini akan menghentikan program — persis kejadian pada chapter Syntax dan Runtime Errors.
  3. Baris 14 (} catch (error) {): bila galat terlempar, object galat diterima di parameter error, dan blok ini berjalan menggantikan sisa blok try.
  4. Baris 15 (console.log("Gagal - " + error.name + ": " + error.message);): alih-alih berhenti, kegagalan dicatat sebagai satu baris keluaran, lalu loop berlanjut ke entri berikutnya.
  5. Baris 19 (console.log("Program selesai dengan normal");): baris ini tercapai — bukti utama bahwa galat tertangkap tidak menghentikan program.

Perhatikan entri ketiga tetap terbaca {"bahasa":"id"} meski entri kedua gagal. Satu kegagalan lokal kini berdampak lokal pula.

Yang ditangkap catch tidak terbatas galat yang dilempar baris di blok try itu sendiri. Galat dari function mana pun yang dipanggil di dalamnya — JSON.parse, atau function buatan kita yang di dalamnya melempar — ikut tertangkap, karena eksekusi menyusuri rantai pemanggil ke bawah dan berhenti di penangkap terdekat. Mekanisme inilah yang kelak menjadi fondasi pola lapisan pada chapter Error Boundaries in Application Design.

Satu batasan penting perlu dicatat sejak awal:

Struktur bloknya juga punya aturan: try tidak boleh berdiri sendiri, minimal didampingi catch atau finally. Ketiadaan keduanya ditolak parser:

js
try {
  console.log("isi try");
}
text
SyntaxError: Missing catch or finally after try

Variabel error Hanya Hidup di catch #

Parameter catch (error) mengikuti aturan scope dari section Functions: variabel itu hanya ada di dalam blok catch. Di luarnya, error tidak dikenal. Jika kebetulan ada variabel bernama sama di luar, variabel tersebut tidak tersentuh — parameter catch menaunginya hanya selama blok berjalan:

js
let error = "milik luar";

try {
  JSON.parse("{ oops }");
} catch (error) {
  console.log("Dalam catch:", error.name);
}

console.log("Di luar catch:", error);

Output yang Dihasilkan:

text
Dalam catch: SyntaxError
Di luar catch: milik luar

Di dalam catch, error merujuk object galat; setelah blok selesai, nama itu kembali menunjuk string "milik luar". Di sini konvensi penamaan ikut membantu: parameter catch (error) adalah pilihan umum, dan banyak kode memakai nama singkat catch (e) bila bloknya pendek. Apa pun namanya, jangan lupa bahwa nama itu hanya sah di dalam bloknya.

Bila detail galat memang tidak dibutuhkan, parameter boleh ditiadakan sepenuhnya:

js
try {
  JSON.parse("{ oops }");
} catch {
  console.log("Tetap tertangkap, tanpa nama variabel");
}

Output yang Dihasilkan:

text
Tetap tertangkap, tanpa nama variabel

finally: Selalu Dijalankan #

Struktur lengkapnya terdiri dari tiga blok berurutan — try, lalu catch, lalu finally. catch dan finally sama-sama opsional, tetapi urutannya tidak bisa ditukar: finally selalu paling akhir, dan minimal salah satu dari keduanya wajib ada. Blok ketiga ini berisi pekerjaan yang harus tuntas apa pun hasil blok try: berhasil, gagal tertangkap, atau bahkan gagal tanpa penangkap.

Bukti paling teliti datang dari kombinasi dengan return. Perhatikan urutan keluaran pada berkas berikut:

// urutan-return.js
// Bukti urutan: finally berjalan sebelum function benar-benar kembali.

function bacaBagian(gagal) {
  try {
    if (gagal) {
      throw new Error("data tidak lengkap");
    }
    console.log("try: data siap");
    return "dari try";
  } catch (error) {
    console.log("catch: " + error.message);
    return "dari catch";
  } finally {
    console.log("finally: selalu dijalankan");
  }
}

console.log(bacaBagian(false));
console.log("---");
console.log(bacaBagian(true));

Output yang Dihasilkan:

text
try: data siap
finally: selalu dijalankan
dari try
---
catch: data tidak lengkap
finally: selalu dijalankan
dari catch

Urutannya konsisten pada dua jalur:

  1. Baris 9–10 (console.log("try: data siap"); lalu return "dari try";): jalur sukses. return di baris 10 menyatakan niat kembali — tetapi function belum benar-benar kembali.
  2. Baris 15 (console.log("finally: selalu dijalankan");): blok finally berjalan sebelum nilai return di antar ke pemanggil. Karena itu finally: selalu dijalankan tercetak lebih dulu, baru dari try.
  3. Baris 7 (throw new Error("data tidak lengkap");): jalur gagal. Eksekusi lompat ke catch, yang mencatat pesan dan menyiapkan return miliknya.
  4. Baris 15 lagi: finally tetap berjalan di jalur ini — inilah jaminannya, tidak peduli jalur mana yang ditempuh.

Bentuk paling umum dari finally adalah pembersihan: koneksi ditutup, berkas ditutup, timer dihentikan. Isi finally dipilih dengan satu ujian sederhana: pekerjaan itu harus tuntas pada semua jalur — sukses maupun gagal — agar tidak ada sumber daya yang tertinggal terbuka. Snippet berikut membuktikan bahwa pembersihan tetap terjadi meski galatnya diteruskan ke atas (karena tidak ada catch di function):

js
const log = [];

function prosesData(gagal) {
  log.push("koneksi dibuka");
  try {
    if (gagal) {
      throw new Error("data tidak ditemukan");
    }
    log.push("data dibaca");
  } finally {
    log.push("koneksi ditutup");
  }
}

try {
  prosesData(true);
} catch (error) {
  console.log("Ditangkap di luar:", error.message);
}
console.log(log);

Output yang Dihasilkan:

text
Ditangkap di luar: data tidak ditemukan
[ 'koneksi dibuka', 'koneksi ditutup' ]

Galat data tidak ditemukan tidak tertangkap di dalam prosesData, sehingga naik ke pemanggil — tetapi array log menunjukkan "koneksi ditutup" sempat dicatat lebih dulu. finally berjalan dahulu, barulah galat diteruskan. Bandingkan bila finally ditiadakan: log hanya berisi 'koneksi dibuka', dan pembersihan tidak pernah terjadi.

Jebakan: return di finally #

Karena finally berjalan pada saat terakhir sebelum function kembali, return di dalamnya akan menggeser nilai yang sudah disiapkan blok try. Berkas berikut membuktikannya secara nyata:

// return-di-finally.js
// Demonstrasi: return di finally menggeser hasil yang dikembalikan.

function tentukanBonus() {
  try {
    console.log("try: bonus Rp50000");
    return 50000;
  } finally {
    console.log("finally: bonus diganti Rp10000");
    return 10000;
  }
}

console.log("Bonus diterima:", tentukanBonus());

Output yang Dihasilkan:

text
try: bonus Rp50000
finally: bonus diganti Rp10000
Bonus diterima: 10000

Nilai 50000 sudah tertahan di return milik try, tetapi return 10000 milik finally menimpanya — pemanggil menerima 10000. Lebih parah lagi, return di finally juga menelan galat yang sedang diteruskan: galat yang belum tertangkap diam-diam tidak sampai ke pemanggil. Perilaku itu membuat kegagalan nyata terlihat seperti tidak terjadi apa-apa.

Sampai di sini kita sudah memakai error.name dan error.message berulang kali. Chapter berikutnya membedah benda galat itu sendiri — apa saja yang tersimpan di dalamnya dan jenis-jenis galat bawaan yang perlu dikenali.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

baca-konfigurasi.js

Unduh berkas
javascript
// baca-konfigurasi.js
// try-catch: galat ditangkap, sisa program tetap berjalan.

const pengaturan = [
  '{ "tema": "gelap", "ukuran": 14 }',
  '{ tema: gelap }',
  '{ "bahasa": "id" }',
];

for (const teks of pengaturan) {
  try {
    const data = JSON.parse(teks);
    console.log("OK - terbaca: " + JSON.stringify(data));
  } catch (error) {
    console.log("Gagal - " + error.name + ": " + error.message);
  }
}

console.log("Program selesai dengan normal");

return-di-finally.js

Unduh berkas
javascript
// return-di-finally.js
// Demonstrasi: return di finally menggeser hasil yang dikembalikan.

function tentukanBonus() {
  try {
    console.log("try: bonus Rp50000");
    return 50000;
  } finally {
    console.log("finally: bonus diganti Rp10000");
    return 10000;
  }
}

console.log("Bonus diterima:", tentukanBonus());

urutan-return.js

Unduh berkas
javascript
// urutan-return.js
// Bukti urutan: finally berjalan sebelum function benar-benar kembali.

function bacaBagian(gagal) {
  try {
    if (gagal) {
      throw new Error("data tidak lengkap");
    }
    console.log("try: data siap");
    return "dari try";
  } catch (error) {
    console.log("catch: " + error.message);
    return "dari catch";
  } finally {
    console.log("finally: selalu dijalankan");
  }
}

console.log(bacaBagian(false));
console.log("---");
console.log(bacaBagian(true));