Saat aplikasi menampilkan “Terjadi kesalahan, coba lagi” tanpa penjelasan, hampir bisa dipastikan penulisnya lupa membaca kode status respons. Status 404 dan 500 mengandung informasi berharga — satu mengatakan data tidak ada, yang lain mengatakan server sedang bermasalah — tetapi keduanya baru berguna bila kode kita tahu cara membacanya. Chapter ini melatih pembacaan itu: keluarga status, properti response.ok dan response.status, serta satu perilaku fetch yang mengejutkan hampir semua pemula.
Seperti biasa, kedua berkas contoh menyertakan server tiruan tertanam, jadi node periksa-status.mjs dan node res-tak-dijangkau.mjs bisa langsung Anda jalankan untuk menghasilkan keluaran yang sama.
Empat Keluarga yang Perlu Dikenal #
Digit pertama kode status mengelompokkan nasib permintaan:
2xx— sukses.200 OKuntuk pembacaan dan perubahan biasa,201 CreatedsetelahPOSTberhasil,204 No Contentsaat server sukses tetapi tak punya isi untuk dikirim.3xx— pengalihan.301(pindah permanen) dan302(sementara) menunjuk alamat baru lewat headerLocation;304 Not Modifiedmemberi tahu cache masih layak dipakai.4xx— kesalahan client.400payload tidak sah,401belum terautentikasi,403terautentikasi tetapi tidak diizinkan,404sumber daya tidak ada,422data tidak lolos validasi,429terlalu banyak permintaan.5xx— kesalahan server.500kegagalan internal,502gateway salah balas,503server sementara tidak siap.
Batasnya menegaskan siapa yang perlu bertindak: 4xx berarti permintaan perlu diperbaiki oleh pengirim, 5xx berarti pihak server yang bekerja memulihkan layanannya.
fetch Tidak Melempar Galat untuk 404 atau 500 #
Intuisi banyak pemula: “status galat pasti membuat fetch reject”. Kenyataannya tidak. fetch menyelesaikan Promise-nya (resolve) dengan sukses sepanjang server membalas — apa pun statusnya. Status 404 adalah balasan sah dari server; jaringan bekerja baik, hanya isinya berita buruk.
Berkas berikut membuktikannya dengan tiga rute server tiruan: satu sukses, satu 404, dan satu 500:
// periksa-status.mjs
// fetch TIDAK melempar galat untuk 404/500; kita memeriksa response.ok sendiri.
// Jalankan di terminal: node periksa-status.mjs (tanpa internet).
import http from "node:http";
const server = http.createServer((req, res) => {
if (req.url === "/buku/1") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ id: 1, judul: "Seyum Membaca" }));
return;
}
if (req.url === "/buku/99") {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ pesan: "Buku dengan id 99 tidak ada" }));
return;
}
if (req.url === "/server-gagal") {
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("kesalahan internal server tiruan");
return;
}
if (req.url === "/alamat-lama") {
res.writeHead(301, { Location: "/alamat-baru" });
res.end();
return;
}
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("halaman baru");
});
server.listen(0, "127.0.0.1", async () => {
const port = server.address().port;
const dasar = `http://127.0.0.1:${port}`;
for (const alamat of ["/buku/1", "/buku/99", "/server-gagal"]) {
const response = await fetch(`${dasar}${alamat}`);
console.log(
`${alamat.padEnd(14)} status=${response.status} ok=${response.ok} type=${response.headers.get("content-type")}`
);
}
// Pola pengecekan: fetch tetap resolve, jadi kita yang memutuskan lempar galat.
try {
const response = await fetch(`${dasar}/buku/99`);
if (!response.ok) {
throw new Error(`Permintaan gagal dengan status ${response.status}`);
}
console.log("sampai sini:", await response.json());
} catch (error) {
console.log("tertangkap:", error.message);
}
// 3xx tidak muncul sebagai status akhir: fetch mengikutinya secara otomatis.
const responsePindah = await fetch(`${dasar}/alamat-lama`);
console.log("redirect: status", responsePindah.status, "| redirected:", responsePindah.redirected);
server.closeAllConnections();
server.close();
});Output yang Dihasilkan:
/buku/1 status=200 ok=true type=application/json
/buku/99 status=404 ok=false type=application/json
/server-gagal status=500 ok=false type=text/plain
tertangkap: Permintaan gagal dengan status 404
redirect: status 200 | redirected: trueMari kita telaah baris-baris pentingnya:
- Baris 36–41 (perulangan tiga alamat):
fetchdipanggil tiga kali tanpatry-catch, dan ketiganya selesai tanpa galat. Yang membedakan hanyaresponse.statusdanresponse.ok—okbernilaitruehanya untuk rentang status200–299. - Baris 45–47 (pola pengecekan): karena
fetchtidak akan memutuskan untuk kita, kita yang memeriksaif (!response.ok)dan melemparErrorsendiri, lengkap dengan statusnya di pesan. Barisconsole.logdi dalamtrytidak pernah tercapai untuk/buku/99. - Baris 50–51 (penangkapan): galat buatan kita tertangkap di
catch— pola yang sama akan kita angkat menjadi classApiErrordi chapter API Error Handling. - Baris 23–26 (rute
301): server menjawab301dengan headerLocation: /alamat-baru. Perhatikan keluaranredirect: status akhirnya200, bukan301, danresponse.redirectedbernilaitrue—fetchmengikuti pengalihan secara otomatis sampai respons final diperoleh.
Kegagalan Jaringan: Satu-satunya Rejection #
Kapan fetch benar-benar reject? Ketika permintaan tidak pernah mendapat balasan: alamat tidak terjangkau, koneksi putus di tengah jalan, DNS gagal, atau server sedang tidak menyala. Dalam kasus itu fetch menolak dengan TypeError berpesan "fetch failed".
Berkas kedua membangun situasi itu secara deterministik: server dinyalakan sekadar meminjam satu port kosong, lalu dimatikan, sehingga port itu terlantar tanpa penjaga. fetch ke alamatnya pun pasti ditolak:
// res-tak-dijangkau.mjs
// Satu-satunya kasus di mana fetch melempar galat: jaringan yang tidak terjangkau.
// Jalankan di terminal: node res-tak-dijangkau.mjs (tanpa internet).
import http from "node:http";
// 1. Nyalakan server hanya untuk mendapat satu port kosong.
const server = http.createServer(() => {});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = server.address().port;
// 2. Matikan server; portnya kini tidak dijaga siapa pun.
server.close();
await new Promise((resolve) => server.on("close", resolve));
console.log("port acak itu kini kosong — tidak ada yang mendengarkan.");
// 3. fetch ke port kosong: permintaan tidak pernah sampai ke mana pun.
try {
await fetch(`http://127.0.0.1:${port}/`);
} catch (error) {
console.log("nama :", error.name);
console.log("pesan :", error.message);
console.log("sebab :", error.cause?.code);
}Output yang Dihasilkan:
port acak itu kini kosong — tidak ada yang mendengarkan.
nama : TypeError
pesan : fetch failed
sebab : ECONNREFUSEDMari kita telaah baris-baris pentingnya:
- Baris 9 (
await new Promise(... server.listen(0, ...))):listendibungkusPromiseagar top-levelawaitmenunggu port benar-benar terbuka; angka0kembali dipakai untuk mendapat port acak yang pasti kosong. - Baris 13–14 (
server.close()): server berhenti mendengarkan, danawaitkedua menunggu penutupan selesai. Mulai titik ini, port tersebut tidak lagi dijaga siapa pun. - Baris 19 (
fetch(...)): permintaan dikirim ke port yang kosong. Sistem operasi langsung menolak sambungannya, danfetchmenolak denganTypeError. - Baris 21–23 (baca galat):
error.nameadalah"TypeError"danerror.messageselalu"fetch failed"— generik memang. Detail aslinya diselamatkan dierror.cause: kodeECONNREFUSED(sambungan ditolak) memberi petunjuk teknis yang jauh lebih berguna saat mendiagnosis.
Karena penolakan jenis ini berbentuk lain dari “respons buruk”, pola penanganan yang sehat menggabungkan keduanya: try-catch untuk kegagalan jaringan, if (!response.ok) untuk status galat. Chapter Retry dan Timeout Strategies memanfaatkan perbedaan itu untuk memutuskan mana yang layak diulang — dan chapter berikutnya menyingkap peran header dalam semua percakapan ini.
Sebagai catatan penutup bagian ini: statusText (“OK”, “Not Found”) hanyalah keterangan pelengkap dari server, dan banyak HTTP/2 respons memang tidak membawanya — jangan pernah menjadikannya dasar logika atau pesan untuk pengguna. Angka status dan boolean ok adalah dua-duanya yang bisa diandalkan.
Merancang Status untuk Server Sendiri #
Karena seluruh berkas contoh di section ini membangun server tiruan sendiri, kita sesungguhnya sedang berlatih memilih kode status — keterampilan yang sama nantinya dipakai saat membangun API sungguhan. Beberapa panduan yang sudah kita terapkan di server-server tadi:
- Baca data →
200; buat data baru →201; sukses tanpa isi →204. Mengirim200dengan body kosong untuk operasi hapus membuat client menebak —204menyatakan maksud itu dengan tepat. - Data tidak ada →
404, bukan200berisinull. Client yang memeriksaresponse.okmengandalkan kesetiaan pemetaan ini. - Payload tidak bisa diurai →
400; payload sah tetapi isinya gagal validasi →422. Keduanya sering tertukar, dan keduanya tetap lebih jujur daripada500untuk masalah yang berasal dari client. - Server sementara tidak siap →
503, bukan500. Angka503menyatakan “kondisi ini bisa pulih” — sinyal yang benar bagi mesin retry.
Untuk 429 dan 503, server yang baik sering menyertakan header Retry-After yang mengajukan jeda tunggu — petunjuk yang akan berguna saat kita menyusun strategi retry di chapter berikutnya. Kesesuaian status dengan keadaan sebenarnya inilah yang memungkinkan client ditulis sederhana: cukup percaya pada response.ok dan keluarganya.