menengahasyncerrortry-catchMenengah3 mnt baca

Error Handling di async/await: try...catch yang Benar

Kode asinkron yang rapi tetap bisa gagal. Pelajari try-catch di async function, kapan memakai penjagaan global vs per-operasi, dan pola finally.

Kenapa Perlu Strategi Error Khusus?

Di modul async/await, try-catch menggantikan .catch() di ujung rantai. Tapi seberapa luas try-catch itu? Satu try raksasa untuk 10 operasi memang sederhana, tapi satu kegagalan kecil membatalkan semuanya; try-catch di setiap baris membuat kode berisik dan sulit dibaca.

Jawabannya tergantung: kalau operasi ini gagal, apakah operasi lain masih masuk akal dilanjutkan? Kalau tidak, pakai penjagaan global; kalau ya, tangani per-operasi.

Pola 1: Penjagaan Global (Satu try-catch Besar)

Cocok untuk alur yang saling bergantung: kalau satu tahap gagal, hasil akhirnya tidak valid sehingga tidak ada gunanya lanjut. Contoh: proses checkout warung.

javascript
async function checkout(keranjang) {
  try {
    const stok = await cekStok(keranjang);
    const total = await hitungTotal(stok);
    const struk = await simpanTransaksi(total);
    tampilkanStruk(struk);
  } catch (err) {
    tampilkanError("Checkout gagal: " + err.message);
  }
}

Satu catch menangani kegagalan tahap mana pun: stok habis, hitung error, atau server menolak. Tepat, karena struk tidak bisa dicetak sebelum total dihitung.

Pola 2: Penanganan Per-Operasi

Cocok untuk operasi yang independen: satu gagal, yang lain tetap berharga. Contoh: dashboard kasir memuat ringkasan omzet, daftar menu terlaris, dan notifikasi stok menipis.

javascript
async function muatDashboard() {
  let omzet = "data tidak tersedia";
  try {
    omzet = await ambilOmzet();
  } catch (err) {
    console.log("Omzet gagal dimuat:", err.message);
  }

  let terlaris = [];
  try {
    terlaris = await ambilTerlaris();
  } catch (err) {
    console.log("Menu terlaris gagal dimuat:", err.message);
  }

  tampilkanDashboard({ omzet, terlaris });
}

Dashboard tetap tampil dengan data yang berhasil dimuat plus pesan pengganti untuk yang gagal. Dengan penjagaan global, satu API down membuat seluruh dashboard blank; per-operasi jauh lebih ramah.

Catatan teknis: pola per-operasi bisa dipadatkan dengan helper, misalnya const aman = (p, fallback) => p.catch(() => fallback), lalu const omzet = await aman(ambilOmzet(), "n/a"). Hasilnya sama, kodenya lebih ringkas.

Pola 3: Campuran, Kasus Paling Umum

Dunia nyata biasanya campuran: sebagian operasi kritis (global), sebagian opsional (per-operasi). Contoh realistis proses pesanan warung:

javascript
async function prosesPesanan(pesanan) {
  try {
    // Kritis: gagal di sini berarti pesanan batal total
    const total = await hitungTotal(pesanan);
    const id = await simpanTransaksi(pesanan, total);

    // Opsional: notifikasi boleh gagal, pesanan tetap sah
    try {
      await kirimNotifikasiWA(pesanan);
    } catch (err) {
      console.log("Notifikasi WA gagal, lewati:", err.message);
    }

    return "Pesanan " + id + " berhasil. Total Rp" + total;
  } catch (err) {
    return "Pesanan gagal: " + err.message;
  }
}

Strukturnya bersarang tapi logis: try dalam untuk hal opsional, try luar untuk hal kritis. Pembaca kode langsung paham mana yang boleh gagal dan mana yang tidak.

finally di Dunia async/await

finally bekerja sama seperti di Promise: blok yang selalu jalan untuk bersih-bersih. Kasus klasik: loading spinner.

javascript
async function muatLaporan() {
  tampilkanLoading(true);
  try {
    const data = await ambilLaporan();
    tampilkanTabel(data);
  } catch (err) {
    tampilkanError("Laporan gagal dimuat.");
  } finally {
    tampilkanLoading(false); // selalu jalan, sukses atau gagal
  }
}

Tanpa finally, kamu harus menulis tampilkanLoading(false) dua kali dan berisiko lupa di satu jalur.

Jebakan Umum

  1. Lupa await di dalam try: try { ambilData(); } catch {} tanpa await tidak menangkap apa-apa, karena error terjadi nanti di Promise yang tidak ditunggu. Catch hanya menangkap Promise yang di-await.
  2. try-catch tidak menangkap callback non-async: error yang dilempar di dalam setTimeout(() => { throw ... }) tidak tertangkap try di luarnya. Hanya Promise yang di-await yang terhubung ke try-catch.
  3. Menelan error penting: catch (err) {} kosong membuat bug tak terlihat. Minimal catat ke console; untuk error kritis, lempar ulang dengan throw setelah mencatat.
  4. Satu try raksasa untuk operasi independen: satu API down menenggelamkan semuanya. Pisahkan yang independen ke try-catch per-operasi atau pakai Promise.allSettled.
Live Playground
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

Edit kode di kiri, preview kanan ter-update otomatis.

Tantangan

Try-Catch Bersarang Warung

Di dunia nyata, kemampuan membedakan error kritis dan opsional menentukan apakah aplikasimu tangguh atau rapuh: notifikasi yang gagal tidak boleh membatalkan transaksi yang sudah sah. Pola campuran ini dipakai di checkout, pendaftaran, dan sinkronisasi data. Tugasmu: buat async function bayar(total): di try luar, await prosesBayar() yang resolve "Rp" + total; di dalamnya ada try-catch kecil untuk await kirimStruk() yang selalu reject "printer macet", tangkap dan tulis "Struk gagal dicetak, lanjut." ke #app. Catch luar menampilkan "Bayar gagal." Akhirnya tampilkan hasil bayar di #app.

<!doctype html>
<html>
<head>
<meta charset="utf-8" />

</head>
<body>
<h1>Halo JS</h1>
<p>Buka console preview untuk melihat output.</p>
<script src="index.js"></script>
</body>
</html>