Fetch

Permintaan HTTP GET dan POST, penanganan JSON, serta pemeriksaan status.

Daftar isi

Hampir semua aplikasi web perlu bertukar data dengan server, dan fetch adalah fungsi bawaan browser sekaligus Node.js untuk pekerjaan itu, yaitu mengirim permintaan HTTP dengan gaya berbasis promise. Topik ini mempraktikkan permintaan GET beserta pembacaan JSON-nya, pemeriksaan status HTTP, permintaan POST untuk mengirim data, dan pembedaan error jaringan dari respons yang gagal. Semua contoh dieksekusi terhadap server kecil yang dinyalakan oleh skrip contohnya sendiri, sehingga keluarannya dapat direproduksi apa adanya.

Permintaan GET dan pembacaan JSON#

fetch(url) mengembalikan promise yang terpenuhi dengan objek Response begitu respons server tiba. Objek itu membawa status, header respons, dan badan respons yang belum terbaca, sehingga butuh satu langkah lagi untuk mengambil isinya. Untuk respons JSON, langkah itu adalah res.json(), yang membaca badan respons lalu mengurainya menjadi nilai JavaScript.

js ambil-get.js
// Server lokal kecil sebagai target, lalu permintaan GET dengan fetch.
const http = require("node:http");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ judul: "Belanja mingguan", isi: "kopi, beras, telur" }));
});

server.listen(0, "127.0.0.1", async () => {
  const port = server.address().port;
  const res = await fetch(`http://127.0.0.1:${port}/api/catatan`);
  const data = await res.json();
  console.log("status:", res.status);
  console.log("res.ok:", res.ok);
  console.log("judul:", data.judul);
  console.log("isi:", data.isi);
  server.close();
});
text Keluaran
status: 200
res.ok: true
judul: Belanja mingguan
isi: kopi, beras, telur

Panggilan fetch ditunggu lewat await sebelum badannya dibaca, dan res.json() pun ditunggu dengan await karena proses penguraiannya juga memakan waktu. Server targetnya adalah server sementara yang dibuat modul node:http, dinyalakan pada port bebas yang dipilih sistem lewat argumen 0, lalu dimatikan lagi di akhir. Pola ini dipakai agar contoh tetap mandiri, sedangkan pada aplikasi nyata URL-nya menunjuk ke server sungguhan di internet.

Objek Response tidak hanya membawa badan respons. res.status menyimpan kode statusnya, res.ok merangkum keberhasilannya, dan header responsnya dibaca lewat res.headers.get(), misalnya res.headers.get("content-type") untuk memastikan bentuk isinya sebelum diurai. Pembacaan badan juga tersedia dalam bentuk lain selain res.json(), yaitu res.text() untuk teks biasa dan res.blob() untuk data biner seperti gambar. Pemilihan bentuknya mengikuti isi respons, dan untuk API yang membalas JSON, res.json() adalah pilihan utamanya.

Memeriksa status HTTP dengan res.ok#

Respons yang tiba dari server tidak selalu membawa data yang diminta, sehingga perlu diperiksa lebih dahulu. Properti res.status berisi kode status HTTP, sedangkan res.ok bernilai true hanya untuk rentang 200 sampai 299. Program berikut meminta catatan yang memang tidak ada, sehingga server membalas 404.

js cek-status.js
// Status 4xx tidak melempar error, res.ok hanya bernilai false.
const http = require("node:http");

const server = http.createServer((req, res) => {
  res.writeHead(404, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ pesan: "catatan tidak ditemukan" }));
});

server.listen(0, "127.0.0.1", async () => {
  const port = server.address().port;
  const res = await fetch(`http://127.0.0.1:${port}/api/catatan/99`);
  console.log("status:", res.status);
  console.log("res.ok:", res.ok);
  if (!res.ok) {
    const data = await res.json();
    console.log("pesan server:", data.pesan);
  }
  server.close();
});
text Keluaran
status: 404
res.ok: false
pesan server: catatan tidak ditemukan

Respons 404 tetap diurai seperti biasa, dan pengecekannya dilakukan sebelum data dipakai. Banyak server menyertakan pesan penjelasan pada badan respons gagalnya, sehingga membaca res.json() di dalam cabang !res.ok tetap berguna untuk menampilkannya.

Mengirim data dengan POST#

Permintaan GET hanya membaca, sedangkan mengirim data membutuhkan konfigurasi tambahan pada opsi kedua fetch. method menyatakan metode HTTP-nya, headers menyatakan bentuk badan yang dikirim, dan body berisi datanya yang diserialisasi dengan JSON.stringify. Server pada contoh membalas dengan status 201, kode yang berarti sebuah data baru berhasil dibuat.

js ambil-post.js
// Mengirim data JSON dengan POST, server membalas dengan status 201.
const http = require("node:http");

const server = http.createServer((req, res) => {
  let tubuh = "";
  req.on("data", (potongan) => {
    tubuh += potongan;
  });
  req.on("end", () => {
    const catatan = JSON.parse(tubuh);
    res.writeHead(201, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ pesan: "catatan tersimpan", judul: catatan.judul }));
  });
});

server.listen(0, "127.0.0.1", async () => {
  const port = server.address().port;
  const res = await fetch(`http://127.0.0.1:${port}/api/catatan`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ judul: "Daftar tugas", isi: "belajar fetch" }),
  });
  const data = await res.json();
  console.log("status:", res.status);
  console.log("pesan:", data.pesan);
  console.log("judul:", data.judul);
  server.close();
});
text Keluaran
status: 201
pesan: catatan tersimpan
judul: Daftar tugas

Header Content-Type: application/json menyatakan bentuk data yang dikirim, dan servernya memakai informasi itu saat mengurai badan permintaan. Tanpa header itu, banyak server menolak badan JSON atau salah menafsirkannya, sehingga JSON.stringify pada body dan Content-Type: application/json pada headers sebaiknya selalu ditulis bersama. Untuk bentuk data lain, misalnya unggahan berkas, header dan badannya memang berbeda, tetapi pola opsi kedua fetch ini tetap yang sama.

Server sementara di contoh ini mengumpulkan badan permintaan potongan demi potongan, lalu menguraikannya dengan JSON.parse setelah data selesai diterima. Balasannya membuktikan data sampai utuh, karena judul yang dikirim ikut tercetak pada respons. Pada aplikasi nyata, pola POST seperti ini dipakai untuk membuat data baru, misalnya menambah catatan ke daftar milik pengguna.

Kegagalan jaringan dan contoh API nyata#

Jalur kegagalan yang benar-benar membuat fetch menolak adalah kegagalan jaringan, yaitu ketika permintaan tidak pernah sampai atau tidak pernah dibalas. Penolakannya berupa TypeError dengan pesan singkat, sedangkan detail penyebabnya tersimpan pada properti cause. Penanganannya memakai try/catch di sekeliling await.

js gagal-jaringan.js
// Kegagalan jaringan melempar TypeError, berbeda dari status HTTP 4xx dan 5xx.
async function utama() {
  try {
    await fetch("http://127.0.0.1:41234/api/catatan");
  } catch (err) {
    console.log("jenis error:", err.name);
    console.log("pesan:", err.message);
    console.log("sebab:", err.cause.code);
  }
}

utama();
text Keluaran
jenis error: TypeError
pesan: fetch failed
sebab: ECONNREFUSED

Port 41234 pada contoh sengaja dibiarkan kosong sehingga koneksi ditolak segera, dan kode penyebabnya ECONNREFUSED muncul pada keluaran. Perbedaannya dengan kasus 404 di atas penting untuk dipegang. Status HTTP berarti server menjawab, sedangkan error jaringan berarti percakapan tidak pernah terjadi. Contoh berikut adalah bentuk permintaan ke API publik yang keluarannya bergantung pada isi server saat itu, sehingga tidak dieksekusi di sandbox.

js
// tidak-dijalankan
// Meminta satu data dari API publik, keluarannya bergantung pada server.
fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then((res) => {
    console.log("status:", res.status);
    return res.json();
  })
  .then((data) => console.log("judul tugas:", data.title));

Bentuknya memakai rantai then tanpa await, dan polanya sama persis dengan contoh-contoh lokal di atas, yaitu periksa status, baca badan respons, lalu olah datanya.

Kegagalan jaringan juga mencakup permintaan yang menggantung terlalu lama, sedangkan fetch tidak menyediakan opsi batas waktu yang seragam di semua lingkungan. Cara yang lazim adalah melombakannya dengan timer penolak memakai Promise.race, sehingga permintaan yang tidak kunjung dibalas kalah oleh kegagalan buatan sendiri. Pola itu meminjam kombinator dari topik Kombinator Promise, dan kombinasi fetch dengan timer cukup untuk sebagian besar kebutuhan sehari-hari.

Ringkasan#

  • fetch mengembalikan promise yang terpenuhi dengan objek Response begitu respons server tiba.
  • Badan respons dibaca lewat res.json() yang juga berupa promise, dan hanya dapat dibaca sekali.
  • Status HTTP diperiksa lewat res.status dan res.ok, karena 4xx dan 5xx tidak membuat fetch menolak.
  • Error jaringan melempar TypeError yang ditangkap try/catch, berbeda jalurnya dari respons gagal milik server.

Referensi#