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.
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.
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), laluconst 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:
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.
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
- Lupa
awaitdi 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. - 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. - Menelan error penting:
catch (err) {}kosong membuat bug tak terlihat. Minimal catat ke console; untuk error kritis, lempar ulang denganthrowsetelah mencatat. - Satu try raksasa untuk operasi independen: satu API down menenggelamkan semuanya. Pisahkan yang independen ke try-catch per-operasi atau pakai
Promise.allSettled.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
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>