Menangani Error di Promise: .catch() dan .finally()
Jaringan bisa putus kapan saja. Pelajari pola .catch() terpusat, .finally() untuk bersih-bersih, dan strategi fallback agar aplikasi tetap jalan.
Kenapa Error Handling Asinkron Spesial?
Di kode sinkron, try...catch menangkap semuanya dalam satu blok. Di dunia Promise, error bisa muncul di tahap mana pun: request tahap 1 gagal, parsing tahap 3 melempar, server tahap 5 timeout. Mengecek di setiap tahap mengembalikan kita ke kekacauan callback.
Promise menyelesaikannya dengan elegan: error apa pun mengalir turun sampai .catch() pertama, melewati semua .then() di antaranya. Satu penjaga di ujung cukup untuk seluruh rantai, plus .finally() untuk bersih-bersih yang harus jalan apa pun yang terjadi.
.catch() di Ujung Rantai
function ambilDataKasir() {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Koneksi ke server putus"), 1000);
});
}
ambilDataKasir()
.then((data) => {
console.log("Mengolah:", data);
return data.toUpperCase(); // tidak pernah jalan
})
.then((olah) => {
console.log("Menampilkan:", olah); // dilewati juga
})
.catch((err) => {
console.log("Terjadi masalah:", err); // error mendarat di sini
});Begitu reject terjadi, semua .then() di bawahnya dilewati dan eksekusi melompat ke .catch() terdekat, seperti pelampung yang menangkapmu di titik mana pun kamu jatuh.
.finally(): Bersih-Bersih Tanpa Peduli Hasil
Ada pekerjaan yang harus dilakukan baik sukses maupun gagal: menyembunyikan spinner loading, menutup koneksi, mencatat waktu selesai. .finally() jalan dalam kedua kasus, tanpa perlu menulisnya dua kali:
function simpanTransaksi(data) {
tampilkanLoading(true);
return kirimKeServer(data)
.then((res) => console.log("Tersimpan:", res))
.catch((err) => console.log("Gagal simpan:", err))
.finally(() => {
tampilkanLoading(false); // selalu jalan
console.log("Proses simpan selesai.");
});
}Catatan penting: callback .finally() tidak menerima hasil atau error, dan nilai yang ia kembalikan diabaikan (rantai meneruskan nilai asli). Ia murni untuk efek samping bersih-bersih, bukan transformasi data.
Contoh Realistis: Pola Fallback
Aplikasi tangguh tidak menyerah pada kegagalan pertama; ia punya rencana cadangan: coba ambil data terbaru dari server, kalau gagal pakai data lokal yang tersimpan:
function ambilMenuServer() {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Server tidak merespons"), 1200);
});
}
function ambilMenuLokal() {
return Promise.resolve(["mie goreng (data kemarin)", "es teh (data kemarin)"]);
}
ambilMenuServer()
.catch((err) => {
console.log("Server gagal (" + err + "), pakai data lokal...");
return ambilMenuLokal(); // fallback: kembalikan Promise pengganti
})
.then((menu) => {
console.log("Menu tampil:", menu); // tetap jalan dengan data cadangan
})
.catch((err) => {
console.log("Darurat: data lokal pun gagal:", err);
});.catch() di tengah rantai yang mengembalikan nilai (atau Promise) melanjutkan rantai seolah sukses: tangkap error, sediakan pengganti, rantai lanjut normal.
Catatan teknis: perilaku "catch lalu lanjut" ini pedang bermata dua. Kalau kamu tidak sengaja menaruh
.catch()di tengah rantai tanpa mengembalikan apa-apa, error "hilang" dan tahap berikutnya menerimaundefinedseolah semuanya baik-baik saja. Taruh.catch()pemulih hanya di titik yang memang dirancang sebagai fallback.
Error di Tengah Chain: Throw Itu Menular
throw di dalam callback .then() otomatis mengubah Promise hasil menjadi rejected, sehingga tertangkap .catch() di bawahnya. Ini memungkinkan validasi di tengah rantai:
Promise.resolve({ nama: "mie goreng", harga: -5000 })
.then((item) => {
if (item.harga < 0) {
throw new Error("Harga tidak valid: " + item.harga);
}
return item;
})
.then((item) => console.log("Dijual:", item.nama))
.catch((err) => console.log("Validasi gagal:", err.message));Tidak perlu membungkus setiap tahap dengan try-catch; cukup throw dan biarkan mengalir ke penjaga di bawah.
Jebakan Umum
- Tidak ada
.catch()sama sekali: error hilang diam-diam (atau jadiUnhandledPromiseRejection). Setiap rantai butuh minimal satu.catch(). .catch()di tengah menelan error penting: kalau catch mengembalikan nilai biasa, rantai lanjut seolah sukses. Untuk error yang tidak bisa dipulihkan, lempar ulang denganthrow errdi dalam catch.- Mengira
.finally()bisa mengubah hasil: tidak bisa. Return di finally diabaikan; hasil rantai tetap dari tahap sebelumnya. - Menangkap lalu diam:
.catch(() => {})kosong menyembunyikan bug. Minimal catat ke console atau tampilkan pesan ke pengguna.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
Tantangan
Login dengan Fallback
Di dunia nyata, aplikasi yang baik tidak menampilkan halaman error kosong saat server bermasalah; ia menampilkan data cadangan atau pesan yang membantu. Pola catch-sebagai-fallback ini dipakai aplikasi offline-first di mana-mana. Tugasmu: buat fungsi loginServer() yang selalu reject dengan "Server sibuk", lalu rantai: .catch() yang mengembalikan "Login sebagai tamu", .then() yang menampilkan hasilnya di #app, dan .finally() yang menampilkan "Selesai." 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>