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:

js
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:

text
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:

js
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:

text
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:

js
process.on("unhandledRejection", (alasan) => {
  console.log(`unhandledRejection: ${alasan.message}`);
  process.exit(0);
});

Promise.reject(new Error("koneksi basis data gagal"));

Output yang Dihasilkan:

text
unhandledRejection: koneksi basis data gagal

Yang kedua, uncaughtException: exception terlempar di callback mana pun tanpa try/catch penangkap:

js
process.on("uncaughtException", (err) => {
  console.log(`uncaughtException: ${err.message}`);
  process.exit(0);
});

throw new Error("properti yang diakses tidak ada");

Output yang Dihasilkan:

text
uncaughtException: properti yang diakses tidak ada

Kedua 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:

js
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:

text
meminta laporan...
seolah-olah semua berjalan mulus

Program 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:

JalurPemicuPenanganan tepatBila dibiarkan
Parameter err callbackOperasi callback-style gagalPeriksa err pertama di callbackError TypeError menyesatkan, jauh dari akar masalah
Rejection PromisePromise ditolaktry/catch di sekitar await, atau .catch()unhandledRejection, proses berhenti
Exception di callbackKode callback melempartry/catch di dalam callbackuncaughtException, proses berhenti
.catch(() => {}) kosongKegagalan sengaja diabaikanCatat alasannya secara eksplisitKegagalan 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.