Error pada kode sinkron mudah dijangkau: cukup bungkus dengan try/catch dan semuanya tertangkap. Kode asynchronous memecah asumsi ini — error bisa datang lewat parameter callback, lewat penolakan Promise yang terjadi setelah function penghasilnya lama selesai, bahkan lewat peristiwa tingkat proses ketika error sama sekali tidak tertangani. Chapter penutup Section ini memetakan seluruh jalur error tersebut, membuktikan tiap jalurnya dengan eksperimen, dan menutup dengan filosofi penting Node.js: biarkan proses gagal cepat daripada berjalan dalam keadaan rusak diam-diam.
Error yang Diabaikan pada Callback #
Kasus klasik pembuka: callback error-first yang parameternya tidak diperiksa. Program berikut sengaja membaca berkas yang tidak ada lalu langsung memakai hasilnya:
import { readFile } from "node:fs";
readFile("tidak-ada.json", "utf8", (err, isi) => {
// Anti-pattern: parameter err tidak pernah diperiksa
try {
console.log(`Jumlah karakter: ${isi.length}`);
} catch (e) {
console.log(`Terjadi ${e.name}: ${e.message}`);
}
});Output yang Dihasilkan:
Terjadi TypeError: Cannot read properties of undefined (reading 'length')Pesan yang tercetak menyesatkan: TypeError tentang undefined, padahal masalah aslinya adalah berkas yang tidak ditemukan. Ketika readFile gagal, parameter isi diisi undefined, sehingga akses isi.length melempar error yang jauh dari akar masalahnya. Perhatikan juga bahwa try/catch di sini hanya menangkap error di dalam callback — error dari operasi readFile itu sendiri datang lewat err, saluran yang memang tidak bisa ditangkap try/catch. Perbaikannya sudah kita pelajari di Chapter Pola Callback: periksa err di baris pertama callback dan hentikan alur bila error ada.
Rejection Promise dengan try/catch #
Berita baiknya, async/await menyatukan kembali penanganan error sinkron dan asynchronous dalam satu konstruksi. Function async yang meng-await operasi gagal akan melempar error yang bisa ditangkap try/catch biasa:
import { readFile } from "node:fs/promises";
async function bacaKonfigurasi(namaBerkas) {
const isi = await readFile(namaBerkas, "utf8");
return JSON.parse(isi);
}
try {
const konfigurasi = await bacaKonfigurasi("tidak-ada.json");
console.log(`Mode aplikasi: ${konfigurasi.mode}`);
} catch (e) {
console.log(`Tertangkap oleh try/catch: ${e.message}`);
}Output yang Dihasilkan:
Tertangkap oleh try/catch: ENOENT: no such file or directory, open 'tidak-ada.json'Kali ini pesan yang tercetak jujur menggambarkan akar masalah: ENOENT, berkas tidak ditemukan. Perhatikan alurnya: readFile versi fs/promises menolak (reject) ketika berkas tidak ada; penolakan itu berubah menjadi exception pada titik await di baris 5; exception merambat keluar dari bacaKonfigurasi ke catch di baris 11. Satu blok try/catch yang sama juga akan menangkap SyntaxError dari JSON.parse bila isi berkas bukan JSON valid — dua sumber kegagalan berbeda, satu pintu penanganan.
Peristiwa Tingkat Proses #
Apa yang terjadi bila error asynchronous sama sekali tidak ditangani? Node.js memantau dua kondisi darurat lewat peristiwa pada object process. Yang pertama, unhandledRejection: sebuah Promise ditolak tanpa ada handler yang memprosesnya:
process.on("unhandledRejection", (alasan) => {
console.log(`unhandledRejection: ${alasan.message}`);
process.exit(0);
});
Promise.reject(new Error("koneksi basis data gagal"));Output yang Dihasilkan:
unhandledRejection: koneksi basis data gagalYang kedua, uncaughtException: exception terlempar di callback mana pun tanpa try/catch penangkap:
process.on("uncaughtException", (err) => {
console.log(`uncaughtException: ${err.message}`);
process.exit(0);
});
throw new Error("properti yang diakses tidak ada");Output yang Dihasilkan:
uncaughtException: properti yang diakses tidak adaKedua program di atas sengaja memasang handler lalu memanggil process.exit(0) agar demonstrasi berakhir rapi. Tanpa handler, perilaku bawaan Node.js jauh lebih tegas: unhandledRejection dan uncaughtException sama-sama menghentikan proses dengan kode keluar bukan nol, lengkap dengan laporan error di stderr.
Error yang Tertelan #
Jalur terakhir adalah yang paling senyap: error yang sengaja ditelan oleh handler kosong. Anti-pattern satu baris ini sering ditulis untuk “menenangkan” linter atau menyingkirkan peringatan:
function ambilLaporan() {
return Promise.reject(new Error("token akses kedaluwarsa"));
}
console.log("meminta laporan...");
ambilLaporan().catch(() => {});
console.log("seolah-olah semua berjalan mulus");Output yang Dihasilkan:
meminta laporan...
seolah-olah semua berjalan mulusProgram berakhir tanpa pesan error apa pun, padahal permintaan laporan gagal total. Bagi pengguna, fitur tersebut hanya “tidak jalan”; bagi kita sebagai pengembang, tidak ada jejak apa pun untuk diselidiki. Handler .catch(() => {}) mengubah kegagalan menjadi keheningan — jauh lebih buruk daripada crash yang setidaknya terlihat. Bila memang keputusan sadar untuk mengabaikan (misalnya cache opsional yang gagal diisi), tuliskan eksplisit: catat alasan dengan console.error atau komentar yang menjelaskan mengapa pengabaian itu aman.
Empat jalur error pada kode asynchronous dapat kita rangkum demikian:
| Jalur | Pemicu | Penanganan tepat | Bila dibiarkan |
|---|---|---|---|
Parameter err callback | Operasi callback-style gagal | Periksa err pertama di callback | Error TypeError menyesatkan, jauh dari akar masalah |
Rejection Promise | Promise ditolak | try/catch di sekitar await, atau .catch() | unhandledRejection, proses berhenti |
| Exception di callback | Kode callback melempar | try/catch di dalam callback | uncaughtException, proses berhenti |
.catch(() => {}) kosong | Kegagalan sengaja diabaikan | Catat alasannya secara eksplisit | Kegagalan senyap, sulit didiagnosis |
Referensi resmi untuk pendalaman: peristiwa unhandledRejection , peristiwa uncaughtException , dokumentasi errors Node.js , serta panduan try…catch di MDN .
Dengan ini petualangan kita di dalam runtime asynchronous selesai: event loop, microtask, callback, Promise, timer, worker, dan error — seluruh mesin yang membuat Node.js unik sudah kita bedah satu per satu. Pengetahuan ini bukan untuk disimpan; Section berikutnya mengajak kita menggunakannya membangun aplikasi nyata, dimulai dari yang paling dekat dengan terminal: aplikasi baris perintah.