Jangan Biarkan Aplikasi "Diam-diam Rusak": Teknik Error Handling Async/Await yang Sering Dilewatkan Developer

⏱ - menit baca

 

gambar ilustrasi di atas: perbandingan pola try/catch/finally dasar dengan pendekatan custom error class untuk penanganan error yang lebih spesifik

Salah satu kesalahan paling umum yang dilakukan developer JavaScript adalah menulis kode async/await tanpa penanganan error yang memadai — lalu terkejut ketika aplikasi tiba-tiba crash di production tanpa pesan yang jelas. Padahal, async/await sebenarnya membuat error handling jauh lebih rapi dibanding .then().catch() berantai, asalkan dipakai dengan teknik yang tepat.

Kesalahan Paling Dasar: Lupa try/catch Sama Sekali

Ini terdengar sepele, tapi sangat sering terjadi:

// ❌ Tidak ada penanganan error sama sekali
async function getUser(id) {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json();
  return data;
}

Jika fetch gagal — entah karena jaringan putus atau server down — error ini akan menjadi unhandled promise rejection yang bisa menghentikan alur program secara tidak terduga, atau bahkan membuat seluruh proses Node.js crash tanpa peringatan yang jelas.

Pola Dasar yang Wajib Dikuasai: try/catch/finally

async function getUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) {
      throw new Error(`HTTP error: ${res.status}`);
    }
    return await res.json();
  } catch (err) {
    console.error("Gagal mengambil data user:", err);
    return null;
  } finally {
    setLoading(false);
  }
}

Perhatikan detail penting di sini: fetch() tidak otomatis melempar error hanya karena respons berstatus 404 atau 500 — ia hanya reject jika terjadi kegagalan jaringan. Karena itu, pengecekan res.ok secara manual jadi wajib untuk menangkap error HTTP.

Jangan Perlakukan Semua Error dengan Cara yang Sama

Salah satu peningkatan paling berdampak adalah membedakan jenis error, bukan menangani semuanya secara generik. Di sinilah custom error class jadi berguna:

class NetworkError extends Error {
  constructor(message) {
    super(message);
    this.name = "NetworkError";
  }
}

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

async function submitForm(data) {
  try {
    validateData(data); // bisa throw ValidationError
    await sendToServer(data); // bisa throw NetworkError
  } catch (err) {
    if (err instanceof ValidationError) {
      showFieldError(err.field, err.message);
    } else if (err instanceof NetworkError) {
      showRetryPrompt();
    } else {
      logToSentry(err); // error tak terduga, catat untuk investigasi
    }
  }
}

Dengan pendekatan ini, aplikasi bisa merespons setiap jenis kegagalan dengan cara yang paling sesuai — alih-alih menampilkan pesan error generik untuk semua kasus.

Menangani Banyak Operasi Async Sekaligus

Ketika bekerja dengan beberapa await berurutan, penting memikirkan di mana sebenarnya error harus ditangkap:

// ❌ Satu try/catch besar menyembunyikan sumber error
try {
  const user = await fetchUser(id);
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments(posts[0].id);
} catch (err) {
  console.error("Ada yang gagal, entah di mana:", err);
}

// ✅ Menangkap error dengan konteks yang jelas
async function loadUserData(id) {
  const user = await fetchUser(id).catch(err => {
    throw new Error(`Gagal memuat user: ${err.message}`);
  });
  
  const posts = await fetchPosts(user.id).catch(err => {
    throw new Error(`Gagal memuat posts: ${err.message}`);
  });
  
  return { user, posts };
}

Menambahkan konteks di setiap titik kegagalan membuat proses debugging jauh lebih cepat — dibanding hanya mendapat pesan error generik tanpa tahu operasi mana yang sebenarnya bermasalah.

Retry Otomatis untuk Error yang Bersifat Sementara

Tidak semua error perlu langsung menyerah. Untuk kegagalan jaringan yang sifatnya sementara, pola retry dengan exponential backoff sering jadi solusi yang efektif:

async function fetchWithRetry(url, retries = 3, delay = 1000) {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`Status: ${res.status}`);
    return await res.json();
  } catch (err) {
    if (retries === 0) throw err;
    await new Promise(r => setTimeout(r, delay));
    return fetchWithRetry(url, retries - 1, delay * 2);
  }
}

Jangan Lupakan finally untuk Cleanup

Blok finally sering diabaikan, padahal sangat berguna untuk memastikan sesuatu tetap dijalankan — baik operasi berhasil maupun gagal. Ini paling umum dipakai untuk mematikan status loading:

async function loadData() {
  setLoading(true);
  try {
    const data = await fetchData();
    setData(data);
  } catch (err) {
    setError(err.message);
  } finally {
    setLoading(false); // selalu dijalankan, apapun hasilnya
  }
}

Tanpa finally, developer sering lupa mematikan status loading di jalur error — menyebabkan UI terjebak dalam kondisi loading selamanya ketika terjadi kegagalan.

Checklist Error Handling yang Solid

  1. Selalu bungkus await dengan try/catch, terutama untuk operasi yang berinteraksi dengan jaringan atau sistem eksternal
  2. Periksa res.ok setelah fetch(), karena error HTTP tidak otomatis melempar exception
  3. Gunakan custom error class untuk membedakan jenis kegagalan dan menentukan respons yang sesuai
  4. Tambahkan konteks di setiap titik kegagalan, bukan satu try/catch besar yang menyamarkan sumber masalah
  5. Pertimbangkan retry logic untuk error yang bersifat sementara
  6. Manfaatkan finally untuk memastikan proses cleanup selalu berjalan

Error handling yang baik bukan soal menangkap semua exception secara membabi buta, melainkan soal memahami jenis kegagalan apa yang mungkin terjadi dan merespons masing-masing dengan cara yang tepat. Investasi waktu sedikit lebih banyak di awal untuk menyusun strategi error handling yang solid akan jauh lebih murah dibanding harus melakukan debugging di tengah malam saat aplikasi production tiba-tiba bermasalah.

Ditulis oleh

Inats Studio

💬 Komentar ()

Punya pertanyaan atau tanggapan? Yuk diskusi di kolom komentar.

✍ Tulis Komentar