Hampir semua aplikasi web beneran butuh data yang belum ada di halamannya: daftar produk dari backend sebuah toko, cuaca terkini, profil pengguna, hasil pencarian. Data-data itu tersimpan di sebuah server, dan JavaScript-mu harus mengambilnya lewat jaringan. Alatnya, yang sudah tertanam di setiap browser modern, adalah Fetch API.
fetch() memungkinkanmu mengirim request HTTP lalu membaca apa pun yang dibalas server — biasanya berupa JSON. Ini pengganti modern dari XMLHttpRequest yang dulu ribet dan kaku, dan dibangun di atas promise, jadi ia menyatu rapi dengan sintaks async/await yang mungkin sudah pernah kamu temui. Kalau kata “promise” dan “async” masih terasa samar, santai saja — di sini kita pakai pelan-pelan sambil dijelaskan.
Request pertama
Berikut fetch paling ringkas tapi sudah berguna. Kita minta sedikit data lalu mencetaknya:
async function muatPengguna() {
const response = await fetch("https://api.example.com/users/1");
const data = await response.json();
console.log(data);
}
muatPengguna();
Cuma tiga baris yang bekerja, dan persis ada dua await — bagian inilah yang perlu kamu pelototi pelan-pelan:
await fetch(url)mengirim request lalu menunggu server membalas. Yang dikembalikan ke kamu adalah sebuah objekResponse(berisi header, kode status, dan stream body) — belum datanya sendiri.await response.json()membaca body respons lalu mengubahnya dari teks JSON menjadi objek atau array JavaScript yang sesungguhnya.
Pola dua langkah ini sering bikin orang kaget di percobaan pertama, jadi mari kita pastikan benar-benar nempel.
fetch selesai dua kali — kamu butuh dua await
Salah satu kesalahan awal yang umum adalah mengira await fetch(url) langsung memberimu datanya. Padahal tidak. await pertama memberimu objek Response yang menggambarkan balasan server; body-nya bahkan belum dibaca sama sekali. Kamu lalu panggil await response.json() (atau .text()) untuk benar-benar menarik datanya keluar. Dua await, dua langkah. Lupakan yang kedua, dan yang muncul malah Response { ... } yang membingungkan atau sebuah promise yang menggantung, bukan data yang kamu cari.
fetch dibangun di atas promise
Alasan semua await itu bermunculan adalah karena fetch() mengembalikan sebuah promise — semacam tempat penampung untuk nilai yang belum siap, karena jaringan butuh waktu. await cuma menunda jalannya fungsi sampai promise itu tuntas, lalu menyerahkan hasilnya. (Promise dan async/await kita bahas lebih dalam di artikelnya masing-masing; untuk fetch, biasanya cukup pakai pola di atas.)
Fetch bisa juga ditulis dengan rantai .then() alih-alih async/await, dan gaya ini masih banyak kamu temui di kode orang:
fetch("https://api.example.com/users/1")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
Ini melakukan hal yang sama persis dengan versi async/await. Keduanya sah, tapi async/await terbaca dari atas ke bawah layaknya kode biasa, jadi kita pakai itu untuk sisa artikel ini. Pilih saja mana yang disepakati timmu — cuma jangan mencampur dua gaya itu dalam satu fungsi sampai kamu bingung sendiri.
Membaca respons: .json(), .text(), dan kawan-kawan
Objek Response tidak menebak-nebak body-nya berformat apa — kamu sendiri yang menentukan cara membacanya. Method yang dipanggil bergantung pada apa yang dikirim server:
const response = await fetch(url);
const obj = await response.json(); // parse JSON → objek/array
const str = await response.text(); // teks mentah (HTML, teks biasa, CSV)
const blob = await response.blob(); // data biner (gambar, file)
response.json() jauh paling sering dipakai, karena mayoritas API berbicara dalam JSON. Kalau kamu belum yakin sebuah endpoint mengembalikan apa, mulai saja dengan .text() lalu console.log — kamu bakal lihat payload mentahnya dan bisa memutuskan langkah berikutnya dari situ.
Body hanya bisa dibaca satu kali
Tiap method tadi menghabiskan body respons — ia stream sekali pakai. Panggil response.json() lalu coba response.text() pada respons yang sama, maka panggilan kedua langsung melempar error karena body-nya sudah ludes terbaca. Baca sekali saja, simpan ke satu variabel, lalu pakai ulang variabel itu. Kalau memang butuh body-nya dua kali, response.clone() dulu sebelumnya.
Menangani error — bagian yang paling sering salah
Inilah hal yang paling mengejutkan dari fetch, sekaligus sumber error tak terhitung jumlahnya: fetch tidak menganggap status HTTP error seperti 404 atau 500 sebagai kegagalan. Promise-nya cuma gagal (reject) kalau request memang tidak bisa dilakukan sama sekali — tidak ada jaringan, DNS gagal, atau diblokir browser. Bagi fetch, 404 Not Found atau 500 Internal Server Error itu sebuah perjalanan bolak-balik yang sukses sempurna. Server-nya menjawab; kebetulan jawabannya “tidak ada.”
Artinya, try/catch saja belum cukup. Kamu juga harus memeriksa response.ok (atau response.status) sendiri:
async function muatPengguna(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`Request gagal: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Tidak bisa memuat pengguna:", error.message);
return null;
}
}
response.ok itu boolean yang praktis — bernilai true untuk status mana pun di rentang 200–299, dan false untuk selainnya. Memeriksanya (lalu melempar error buatanmu sendiri saat nilainya false) itulah yang membuat blok catch-mu bisa menangani 404 dengan cara yang sama seperti menangani koneksi yang putus. Lewatkan pemeriksaan ini, dan kodemu bakal santai saja mencoba mem-.json() halaman error lalu jebol di tempat yang justru lebih membingungkan.
Jangan lupa pemeriksaan response.ok
Inilah jebakan fetch nomor satu. try/catch di sekeliling sebuah fetch tidak akan menangkap 404 atau 500 dengan sendirinya — bagi fetch, itu respons yang “sukses”. Tanpa if (!response.ok), kodemu jalan terus seolah semua baik-baik saja, lalu ambruk belakangan saat mencoba membaca data yang sebenarnya tidak ada. Jadikan pemeriksaan response.ok sebagai refleks di setiap fetch yang kamu tulis.
Mengirim data: request POST
Sejauh ini kita baru membaca data (request GET, yang jadi default-nya fetch). Untuk mengirim data — bikin akun, submit form, menyimpan record — kamu pakai request POST dan menyodorkan argumen kedua ke fetch berisi detailnya:
async function buatPengguna(user) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user),
});
if (!response.ok) {
throw new Error(`Gagal membuat pengguna: ${response.status}`);
}
return await response.json();
}
const penggunaBaru = await buatPengguna({ name: "John Doe", role: "editor" });
console.log(penggunaBaru);
Ada tiga hal yang membuat POST berfungsi, dan semuanya penting:
method: "POST"memberitahu server bahwa kamu sedang mengirim data, bukan sekadar memintanya.headersdengan"Content-Type": "application/json"memberitahu server cara menafsirkan body. Tanpa ini, banyak server tidak akan memproses JSON-mu dengan benar.body: JSON.stringify(user)— dan inilah yang paling gampang kelupaan — body wajib berupa string, bukan objek JavaScript.JSON.stringifymengubah objekmu menjadi teks JSON. Sodorkan objek mentah ke fetch, dan yang terkirim bukan yang kamu harapkan.
Pola yang sama berlaku untuk PUT (mengganti record), PATCH (mengubah sebagiannya), dan DELETE — tinggal ganti method-nya. Fetch API memakai satu pola yang konsisten untuk segala jenis request, dan justru itulah salah satu alasan ia enak dipakai.
Stringify pas keluar, parse pas masuk
Ada simetri rapi yang layak kamu ingat. Saat mengirim data, kamu JSON.stringify() objekmu jadi string untuk body. Saat menerima data, response.json() mengurai string itu kembali menjadi objek untukmu. Keluar: objek → string. Masuk: string → objek. Kalau kamu sudah baca artikel JSON, ini persis JSON.stringify dan JSON.parse yang sedang bekerja — fetch cuma menyambungkannya ke jaringan. JSON formatter juga berguna kalau kamu ingin memelototi respons yang berantakan secara manual.
Contoh lengkap yang realistis
Mari kita rangkai semuanya jadi sesuatu yang mungkin benar-benar kamu rilis — memuat daftar produk, menampilkannya, dan menangani kemungkinan gagal:
async function muatProduk() {
try {
const response = await fetch("https://api.example.com/products");
if (!response.ok) {
throw new Error(`Server membalas ${response.status}`);
}
const produk = await response.json();
produk.forEach((item) => {
console.log(`${item.name} — Rp ${item.price}`);
});
return produk;
} catch (error) {
console.error("Gagal memuat produk:", error.message);
tampilkanPesanError("Produk gagal dimuat. Coba lagi, ya.");
return [];
}
}
muatProduk();
Perhatikan betapa wajarnya ini bersandar pada hal-hal yang sudah kamu kuasai. Respons-nya berupa array berisi objek — bentuk paling umum dari data nyata — dan kita menyusurinya dengan perulangan (forEach) untuk membaca property tiap produk. Fetch tidak menggantikan apa pun yang kamu pelajari; ia cuma mengantarkan datanya, dan setelah itu semuanya JavaScript biasa. Adanya try/catch plus cadangan return [] membuat gangguan jaringan ditangani dengan anggun, bukan malah membuat halamannya jebol.
Menyetel header request untuk autentikasi
Banyak API beneran mengharuskanmu mengidentifikasi diri dengan sebuah token. Token itu kamu tambahkan sebagai header, persis seperti cara menyetel Content-Type:
const response = await fetch("https://api.example.com/profile", {
headers: {
"Authorization": "Bearer TOKEN_KAMU_DI_SINI",
"Accept": "application/json",
},
});
Header Authorization adalah tempat standar sebuah token diletakkan. Format pastinya (Bearer ..., sebuah API key, dan lain-lain) ditentukan oleh API yang kamu panggil — selalu cek dokumentasinya. Tapi jangan menanam token rahasia asli ke dalam kode front-end yang dikirim ke browser, ya; siapa pun bisa membacanya di sana. Urusan seperti itu tugasnya backend.
Penutup
Sekarang kamu sudah bisa berkomunikasi dengan jaringan lewat JavaScript. Intinya:
fetch(url)mengirim request HTTP dan mengembalikan promise berisi objekResponse. Pakai denganasync/awaitsupaya kodenya enak dibaca.- Ia butuh dua await:
await fetch(...)untuk respons-nya, laluawait response.json()(atau.text(),.blob()) untuk membaca body. Body cuma bisa dibaca satu kali. - fetch tidak menganggap 404/500 sebagai gagal. Selalu cek
if (!response.ok)lalu lempar error, supayatry/catch-mu benar-benar menangkap error HTTP — bukan cuma kegagalan jaringan. - Untuk mengirim data, sodorkan argumen kedua:
method,headers(denganContent-Type), danbody: JSON.stringify(...). Pola yang sama berlaku untuk POST, PUT, PATCH, dan DELETE. - Keluar: objek →
JSON.stringify→ string. Masuk: string →response.json()→ objek.
Fetch adalah pintu antara JavaScript-mu dan seluruh isi internet. Padukan dengan array, objek, dan perulangan yang sudah kamu kuasai, bungkus setiap request dengan penanganan error yang benar, dan kamu bisa membangun aplikasi yang menarik data secara langsung sekaligus tetap berdiri tegak saat jaringan sedang rewel.