Error handling yang baik membuat aplikasi robust tanpa menyembunyikan bug. Prinsipnya: fail fast, fail loud — lebih baik error yang jelas daripada bug silent yang muncul 3 hari kemudian.
Sebelum — error handling yang buruk:
// BURUK 1: catch kosong — menyembunyikan bug!
try {
const data = JSON.parse(response);
processData(data);
} catch (e) {
// tidak melakukan apa-apa...
// bug tersembunyi, data tidak diproses, user tidak tahu
}
// BURUK 2: catch terlalu luas
try {
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
const report = generateReport(orders);
await sendEmail(user.email, report);
} catch (e) {
console.log("Something went wrong"); // apa yang salah? di mana?
}
// BURUK 3: return null tanpa alasan jelas
function findUser(id) {
const user = db.users.find(u => u.id === id);
return user || null; // caller tidak tahu kenapa null
}
Sesudah — error handling yang baik:
// BERSIH 1: tangani error secara spesifik
try {
const data = JSON.parse(response);
processData(data);
} catch (error) {
if (error instanceof SyntaxError) {
console.error("Response bukan JSON valid:", response);
showUserError("Data tidak bisa diproses");
} else {
throw error; // re-throw error yang tidak dikenal
}
}
// BERSIH 2: error di level yang tepat
async function loadDashboard(userId) {
const user = await fetchUser(userId); // biarkan throw jika gagal
const orders = await fetchOrders(user.id); // biarkan throw jika gagal
try {
await sendWelcomeEmail(user.email);
} catch (emailError) {
// Email gagal tidak fatal — log dan lanjutkan
console.warn("Welcome email gagal:", emailError.message);
}
return { user, orders };
}
Custom Error classes:
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NotFoundError extends Error {
constructor(resource, id) {
super(`${resource} with id ${id} not found`);
this.name = "NotFoundError";
this.resource = resource;
this.id = id;
}
}
function getUser(id) {
const user = db.users.find(u => u.id === id);
if (!user) throw new NotFoundError("User", id);
return user;
}
// Caller bisa handle berdasarkan tipe
try {
const user = getUser(123);
} catch (error) {
if (error instanceof NotFoundError) {
showNotFound(error.message);
} else if (error instanceof ValidationError) {
showValidationError(error.field, error.message);
} else {
throw error; // unexpected error — biarkan bubble up
}
}
Guard clauses — fail fast di awal fungsi:
// BURUK: nested validation
function createOrder(user, items, address) {
if (user) {
if (items && items.length > 0) {
if (address) {
// ... actual logic buried deep inside
} else { throw new Error("No address"); }
} else { throw new Error("No items"); }
} else { throw new Error("No user"); }
}
// BERSIH: guard clauses
function createOrder(user, items, address) {
if (!user) throw new ValidationError("user", "User wajib diisi");
if (!items?.length) throw new ValidationError("items", "Items wajib diisi");
if (!address) throw new ValidationError("address", "Alamat wajib diisi");
// Happy path — kode utama tidak ter-nest
const order = { user, items, address, total: calculateTotal(items) };
return db.orders.insert(order);
}
Aturan error handling:
- Jangan catch error yang tidak kamu tahu cara handle-nya — biarkan bubble up
- Jangan catch kosong — minimal log error-nya
- Gunakan custom Error untuk error yang spesifik ke domain kamu
- Fail fast — validasi di awal, jangan di tengah-tengah logic
- Error message harus actionable — "Email format invalid" bukan "Error occurred"
🎭 Analogi sehari-hari
Error handling itu kayak alarm asap di rumah. Alarm yang baik = bunyi keras pas ada api, jelas lokasinya, action-able ("Asap di dapur, cek kompor"). Alarm jelek = bunyi pas masak biasa (false positive), atau gak bunyi pas kebakaran beneran (silent failure). Silent catch (try {} catch (e) {}) = matiin alarm asap, "biar gak ribut" — pas beneran kebakaran kamu gak tau, semuanya keburu hangus. Generic error ("Something went wrong") = alarm yang cuma bunyi tanpa info — kamu tau ada masalah tapi gak tau di mana, mau action apa. Custom error ("Validasi email gagal di field user.email pada step register") = alarm yang nunjuk tepat — fix dalam 30 detik.
⚠️ Jebakan yang sering ditemui
- Silent catch —
catch (e) {}tanpa log = bug ngumpet selama-lamanya. Minimum:console.error(e)ataulogger.error(e). - Catch terlalu luas —
try { entireFunction() } catch (e) { showGeneric() }ngumpet bug. Catch SPESIFIK error yang bisa di-handle. throw "string"instead ofthrow new Error()— string gak punya stack trace. SelaluErroratau subclass.- Generic error message — "Error occurred" useless buat user + dev. "Email format invalid" lebih actionable.
- Catch + ignore async —
fetch().then(...)tanpa.catch()= unhandled promise rejection. Pakaitry/catchdenganawaitatau.catch(). - Re-throw tanpa preserve stack —
throw new Error("wrapped")di catch kehilangan original stack. Pakaithrow new Error("wrapped", { cause: e })(modern). - Validate di tengah logic — bukan fail fast. Validate di TOP function (guard clauses).
- Error yang gak actionable buat user — tampilin "ECONNREFUSED" ke user normal = bingung. Tampilin "Tidak bisa menghubungi server, coba lagi nanti".
Tipe Error yang Harus Beda Handling
class ValidationError extends Error {
constructor(field, message) {
super(message)
this.field = field
this.name = "ValidationError"
}
}
class NotFoundError extends Error {
constructor(resource, id) {
super(`${resource} dengan ID ${id} tidak ditemukan`)
this.name = "NotFoundError"
}
}
class AuthError extends Error {
constructor(message = "Tidak terotorisasi") {
super(message)
this.name = "AuthError"
}
}
🎯 Catch error atau biarin bubble up?
- Bisa recover dengan action konkret (retry, fallback, default value) → catch + handle
- User butuh tahu pesan ramah (form error, network down) → catch + tampilkan
- Error developer (bug code) → biarin bubble up ke ErrorBoundary global
- Programmatic error (TypeError, ReferenceError) → biarin bubble up
- Unknown error type → log + re-throw (jangan silent swallow)
- Async function tanpa await → wrap dalam Promise.catch atau wajib
awaitdi try/catchAturan: catch harus PRODUKTIF (handle, log, transform). Catch yang cuma ngumpet error = pasti bug nanti.
TL;DR: Error handling = fail fast + fail loud. Pakai try/catch cuma kalau bisa handle konkret (retry, fallback, message). Custom Error class per domain (ValidationError, NotFoundError) bedain handling. JANGAN silent catch (catch (e) {}) — minimum log. Guard clauses (validate di top fungsi) > nested if. Error message actionable (sebut field/lokasi/cara fix). Async error WAJIB await di try/catch atau .catch().