Menu

try/catch JavaScript: Menangani Error Tanpa Crash

Cara kerja try/catch/finally di JavaScript: menangkap error, object error, melempar ulang, dan kapan try/catch bukan alat yang tepat.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

try/catch adalah jaring pengaman

Saat sebaris JavaScript melempar error, eksekusi langsung berhenti dan error itu merambat naik ke call stack. Kalau tidak ada yang menangkapnya, program crash (di Node) atau menampilkan deretan pesan merah di console (di browser). try/catch adalah cara mencegatnya, cara mengatakan "aku tahu ini mungkin gagal, dan inilah yang ingin kulakukan sebagai gantinya."

Bentuk dasarnya:

JSON.parse melempar SyntaxError. Eksekusi langsung melompat ke blok catch dengan error terikat ke err. console.log ketiga tetap berjalan: crash-nya berhasil dibendung.

Kalau blok try berhasil tanpa melempar error, blok catch dilewati sepenuhnya. Ia ada untuk jalur kegagalan.

Object error

Apa pun yang dilempar akan terikat ke nama parameter di catch (...). Biasanya ia instance Error dengan tiga field yang berguna:

name memberitahumu subclass-nya (TypeError, RangeError, SyntaxError, dan seterusnya; penjelasannya ada di dokumen berikutnya). message adalah deskripsi yang bisa dibaca manusia. stack adalah jejak lengkapnya, yang sangat berharga saat debugging.

Satu jebakan: JavaScript membolehkanmu melakukan throw apa saja, bukan hanya object Error. Kode lama kadang menulis throw "something broke". Saat kamu menulis throw sendiri, selalu lempar Error supaya pemanggil mendapat stack trace:

finally berjalan dalam kedua kasus

finally adalah blok ketiga yang opsional, yang berjalan entah ada error yang dilempar atau tidak, dan entah catch menanganinya atau tidak. Gunanya untuk bersih-bersih: menutup file, melepas lock, menyembunyikan spinner loading:

Spinner-nya disembunyikan entah pemuatan berhasil atau gagal. Tanpa finally, kamu harus menulis baris itu di kedua cabang, dan lupa di salah satunya.

finally bahkan tetap berjalan kalau blok try atau catch berisi return. Fungsinya baru kembali setelah finally dijalankan. Kadang ini mengejutkan; tapi sebagian besar waktu, justru inilah yang kamu mau.

Kamu tidak selalu butuh catch

catch itu opsional. try/finally tanpa catch itu sah dan berguna saat kamu ingin bersih-bersih yang pasti terjadi tapi tidak berniat menangani error-nya, karena kamu ingin error itu tetap merambat:

try/finally di dalam melepas lock bahkan saat fn() melempar error, tapi tidak menelan error-nya: pemanggil tetap melihatnya. Menelan error diam-diam ("gagal, tapi aku tidak memberi tahu siapa pun") adalah salah satu mimpi buruk debugging yang paling umum.

Melempar ulang: tangani sebagian, teruskan sisanya

Blok catch tidak harus menangani semuanya. Kamu bisa memeriksa error-nya, menangani yang masuk akal, lalu melempar ulang sisanya:

Pengecekan instanceof itulah polanya: kenali error yang kamu tahu cara memulihkannya, dan biarkan yang lain terus naik ke stack. Menelan setiap error dengan blok catch kosong adalah code smell: kamu kehilangan semua sinyal saat terjadi sesuatu yang tidak terduga.

try/catch dengan async/await

Di dalam fungsi async, promise yang di-await lalu reject berubah menjadi error yang dilempar, dan try/catch menanganinya sama seperti error synchronous:

Satu hal halus: kamu harus melakukan await pada promise di dalam blok try. Kalau kamu mengembalikan promise tanpa await, rejection-nya terjadi setelah fungsi sudah keluar, dan catch tidak pernah melihatnya:

async function bad() {
  try {
    return fetch("/broken");  // no await - caller sees the rejection
  } catch (err) {
    // never runs
  }
}

Patokannya: di fungsi async, lakukan await pada hal yang ingin kamu lindungi dengan try/catch.

try/catch bersarang

Kamu bisa menyarangkan blok try/catch saat kode bagian dalam dan luar gagal karena alasan berbeda yang ingin kamu tangani secara berbeda:

catch di dalam menangani "bentuk datanya salah" dengan mengembalikan nilai default yang aman. Yang di luar menangani "input-nya sama sekali bukan JSON" dengan membungkus lalu melempar ulang. Bersarang tidak masalah saat setiap lapisan punya strategi pemulihan yang berbeda; kalau kedua blok akan melakukan hal yang sama, ratakan saja.

Kapan tidak memakai try/catch

try/catch adalah alat untuk kegagalan yang diperkirakan dan bisa dipulihkan. Ia bukan cara untuk menutupi bug.

  • Jangan membungkus seluruh badan fungsi "untuk jaga-jaga." Kalau kamu tidak punya rencana nyata untuk error-nya, biarkan ia naik: error yang tidak tertangkap dengan stack trace lebih berguna daripada error yang diam.
  • Jangan memakainya untuk alur kontrol. Blok try punya overhead nyata dan membuat kode lebih keruh dibanding pengecekan if. if (user) lebih baik daripada try { user.name } catch {}.
  • Jangan menangkap lalu sekadar mencatat dan mengabaikan. Setidaknya, lempar ulang atau kembalikan nilai penanda yang bisa dideteksi pemanggilmu.

Tes mentalnya: "apa yang dilakukan pemakai kode ini saat kode ini gagal?" Kalau kamu belum punya jawabannya, kamu belum siap melakukan catch pada error itu.

Rujukan singkat

  • try { ... } catch (err) { ... }: mencegat error yang dilempar.
  • finally { ... }: selalu berjalan; pakai untuk bersih-bersih.
  • throw new Error("..."): selalu lempar subclass Error supaya stack trace berfungsi.
  • throw err; di dalam catch: lempar ulang saat kamu tidak bisa menanganinya.
  • await di dalam try: wajib supaya try/catch bisa melihat rejection async.

Berikutnya: tipe error

TypeError, RangeError, SyntaxError: JavaScript punya keluarga class error bawaan, dan mengetahui arti masing-masing membuat penangkapan dan pelaporan error jauh lebih tepat. Itulah dokumen berikutnya.

Pertanyaan yang sering diajukan

Bagaimana cara kerja try/catch di JavaScript?

Taruh kode yang berisiko di dalam try { ... }. Kalau ada yang melempar error di dalamnya, eksekusi langsung melompat ke blok catch (err) { ... } dengan nilai yang dilempar terikat ke err. Kalau tidak ada yang melempar error, blok catch dilewati. finally { ... } yang opsional berjalan dalam kedua kasus, berguna untuk bersih-bersih.

Kapan sebaiknya memakai try/catch di JavaScript?

Pakai di sekitar operasi yang memang bisa gagal saat runtime: JSON.parse pada input yang tidak bisa dipercaya, respons fetch, I/O file atau jaringan. Jangan membungkus setiap baris: kalau kamu tidak punya rencana untuk memulihkan keadaan, biarkan error-nya naik. try/catch yang terlalu luas di sekitar kode yang berjalan baik justru menyembunyikan bug, bukan menanganinya.

Apakah try/catch menangkap error async?

Hanya kalau kamu melakukan await pada promise di dalam blok try. Pemanggilan somePromise() tanpa await tidak akan tertangkap: error-nya menjadi unhandled rejection. Dengan async/await, try/catch bekerja sama seperti pada kode synchronous. Untuk promise biasa, pakai .catch() pada rantainya.

Bagaimana cara melempar ulang error di JavaScript?

Di dalam catch, cukup throw err; (atau throw error baru yang membungkusnya). Ini berguna saat kamu ingin menangani sebagian error dan meneruskan sisanya: cek dulu tipe atau pesan error-nya, tangani yang bisa kamu tangani, lalu lempar ulang sisanya supaya pemanggil di atas tetap melihatnya.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai