Menguasai Fetch API: Jurus Rahasia Data Fetching Modern yang Robust dan Efisien
Di era aplikasi web modern, data adalah jantungnya. Hampir setiap aplikasi membutuhkan komunikasi dengan server untuk mengambil, mengirim, atau memperbarui informasi. Sejak kemunculannya, Fetch API telah menjadi standar de facto bagi developer JavaScript untuk melakukan operasi networking ini. Ia menawarkan antarmuka Promise-based yang lebih bersih dan fleksibel dibandingkan pendahulunya, XMLHttpRequest.
Namun, apakah Anda sudah benar-benar menguasai Fetch API? Banyak developer mungkin hanya menggunakan dasar-dasarnya, padahal Fetch API menyimpan banyak jurus rahasia yang bisa membuat aplikasi Anda lebih robust, efisien, dan memberikan pengalaman pengguna yang lebih baik.
Dalam artikel ini, kita akan menyelami Fetch API lebih dalam. Kita akan memulai dari dasar, kemudian bergerak ke opsi konfigurasi lanjutan, strategi untuk membangun request yang tahan banting, hingga bagaimana membangun lapisan abstraksi kustom yang akan meningkatkan produktivitas Anda. Mari kita mulai!
1. Pendahuluan
Sebelum Fetch API, XMLHttpRequest (XHR) adalah cara utama untuk melakukan request HTTP secara asinkron di browser. Meskipun fungsional, XHR memiliki sintaks yang verbose dan sering kali mengarah pada “callback hell” yang sulit dikelola, terutama untuk operasi yang kompleks.
Fetch API hadir sebagai penyegaran. Dengan antarmuka berbasis Promise, ia membuat request HTTP terasa lebih modern dan sejalan dengan paradigma JavaScript asinkron saat ini (async/await). Fetch tidak hanya lebih mudah dibaca, tetapi juga lebih fleksibel, memungkinkan Anda mengontrol hampir setiap aspek dari request HTTP Anda.
📌 Mengapa Fetch API Penting untuk Dikuasai?
- Standar Web: Ini adalah bagian dari standar web, didukung secara luas di browser modern dan Node.js (sejak versi 18 secara global, atau melalui polyfill).
- Fleksibilitas: Memberikan kontrol granular atas request dan response.
- Keterbacaan Kode: Sintaks Promise-based membuat kode lebih bersih dan mudah dipahami.
- Fondasi: Banyak library data fetching populer seperti
React QueryatauSWRmenggunakanfetchdi balik layarnya. Memahamifetchakan membantu Anda memahami library tersebut lebih baik.
2. Memahami Mekanisme Dasar fetch: Promise, Response, dan Penanganan Error
Mari kita mulai dengan menyegarkan ingatan tentang dasar-dasar fetch.
// GET request sederhana
fetch('https://api.example.com/data')
.then(response => {
// response.ok adalah true jika status code 2xx
if (!response.ok) {
// Melempar error untuk status non-2xx (misal: 404, 500)
throw new Error(`HTTP error! status: ${response.status}`);
}
// Mengembalikan Promise yang akan resolve dengan data JSON
return response.json();
})
.then(data => {
console.log('Data berhasil diambil:', data);
})
.catch(error => {
// Menangani error jaringan atau error yang dilempar dari .then()
console.error('Terjadi kesalahan saat fetching data:', error);
});
✅ Poin Penting:
fetch()mengembalikan sebuahPromise.Promiseini akanresolvedengan objekResponsesegera setelah server merespons dengan header HTTP, bukan setelah seluruh body response diterima.Response.okadalah properti boolean yang menunjukkan apakah request berhasil (status kode 200-299).Response.json(),Response.text(),Response.blob(), dll., adalah metode untuk membaca body response. Metode ini juga mengembalikanPromise.fetch()hanya akanrejectjika ada masalah jaringan (misalnya, offline, DNS lookup gagal). Ia tidak akanrejectuntuk status HTTP error (seperti 404 Not Found atau 500 Internal Server Error). Anda harus secara eksplisit memeriksaresponse.okdan melempar error sendiri.
💡 Tips: Gunakan async/await untuk Kode yang Lebih Bersih
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Data berhasil diambil:', data);
} catch (error) {
console.error('Terjadi kesalahan saat fetching data:', error);
}
}
fetchData();
Penggunaan async/await membuat alur kode asinkron lebih linear dan mudah dibaca, mirip dengan kode sinkron.
3. Mengontrol Request Anda dengan Opsi Konfigurasi Lanjutan
fetch() menerima argumen kedua berupa objek options yang sangat powerful untuk mengontrol perilaku request.
fetch(url, options)
Mari kita lihat beberapa opsi penting:
a. method
Menentukan metode HTTP (GET, POST, PUT, DELETE, dll.). Default-nya adalah GET.
fetch('https://api.example.com/articles/1', {
method: 'DELETE' // Mengirim request DELETE
});
b. headers
Mengatur header HTTP untuk request. Sangat penting untuk authentication, tipe konten, dll.
fetch('https://api.example.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // Memberitahu server bahwa body adalah JSON
'Authorization': 'Bearer YOUR_JWT_TOKEN' // Mengirim token autentikasi
},
body: JSON.stringify({ title: 'Artikel Baru', content: 'Isi artikel...' })
});
⚠️ Perhatian Content-Type: Saat mengirim data POST/PUT, pastikan header Content-Type sesuai dengan format body yang Anda kirim (misalnya, application/json untuk JSON.stringify(), atau application/x-www-form-urlencoded untuk data form tradisional).
c. body
Data yang akan dikirim bersama request (untuk metode seperti POST, PUT). Bisa berupa String, FormData, Blob, BufferSource, atau URLSearchParams.
// Mengirim FormData (untuk upload file atau data form kompleks)
const formData = new FormData();
formData.append('username', 'developer_handal');
formData.append('profile_picture', myFileInput.files[0]);
fetch('https://api.example.com/profile', {
method: 'POST',
body: formData // Fetch API akan otomatis mengatur Content-Type: multipart/form-data
});
d. credentials
Mengatur bagaimana request menangani cookie dan authentication header lintas origin.
'omit'(default): Jangan mengirim cookie atau authentication header lintas origin.'same-origin': Kirim cookie dan authentication header hanya untuk request ke origin yang sama.'include': Selalu kirim cookie dan authentication header, bahkan untuk request lintas origin. Ini penting untuk CORS dengan kredensial.
fetch('https://api.example.com/protected-data', {
credentials: 'include' // Penting untuk sesi berbasis cookie di domain lain
});
💡 Tips: Jika Anda mengalami masalah CORS dengan cookies atau authentication header, pastikan server juga merespons dengan header Access-Control-Allow-Credentials: true dan Access-Control-Allow-Origin yang spesifik (bukan *).
e. mode
Mengatur mode CORS untuk request.
'cors'(default): Memungkinkan request lintas origin dengan kebijakan CORS standar.'no-cors': Digunakan untuk request lintas origin yang tidak memerlukan CORS preflight atau header respons CORS. Response akan memilikitype: 'opaque', artinya Anda tidak bisa mengakses header atau statusnya. Biasanya tidak direkomendasikan untuk data fetching yang membutuhkan response yang bisa diakses.'same-origin': Hanya mengizinkan request ke origin yang sama.
fetch('https://some-cdn.com/image.jpg', {
mode: 'cors' // Pastikan server CDN mengizinkan CORS
});
f. cache
Mengontrol bagaimana request berinteraksi dengan cache HTTP browser.
'default'(default): Browser akan memeriksa cache HTTP.'no-store': Tidak menggunakan cache browser sama sekali. Selalu mengambil dari server.'reload': Selalu mengambil dari server, tetapi akan memperbarui cache browser.'no-cache': Memeriksa cache, tetapi akan melakukan validasi ulang dengan server sebelum menggunakan cache (menggunakanIf-None-MatchatauIf-Modified-Since).'force-cache': Menggunakan cache tanpa validasi ulang, bahkan jika sudah kadaluarsa. Hanya akan melakukan request jaringan jika tidak ada cache yang cocok.'only-if-cached': Hanya menggunakan cache. Jika tidak ada cache yang cocok, request akan gagal. Berguna untuk mode offline.
fetch('https://api.example.com/static-config', {
cache: 'no-cache' // Selalu validasi ulang dengan server
});
4. Membangun Request yang Robust: AbortController dan Timeout
Aplikasi yang baik harus mampu menangani skenario di mana request jaringan membutuhkan waktu terlalu lama atau tidak lagi relevan (misalnya, pengguna berpindah halaman). AbortController adalah jurus rahasia untuk ini.
a. AbortController: Membatalkan Request yang Sedang Berjalan
Bayangkan AbortController sebagai tombol “batalkan” untuk request Anda.
const controller = new AbortController();
const signal = controller.signal; // Signal ini akan dilewatkan ke fetch
async function fetchWithAbort() {
try {
const response = await fetch('https://api.example.com/long-running-task', {
signal: signal // Menerima signal pembatalan
});
// Jika request dibatalkan, Promise di atas akan reject sebelum sampai sini
const data = await response.json();
console.log('Data berhasil diambil:', data);
} catch (error) {
if (error.name === 'AbortError') {
console.warn('Fetch request dibatalkan!');
} else {
console.error('Terjadi kesalahan saat fetching data:', error);
}
}
}
fetchWithAbort();
// Setelah beberapa waktu atau karena event tertentu (misal: unmount komponen)
// Kita bisa membatalkan request-nya
setTimeout(() => {
controller.abort(); // Membatalkan request!
}, 2000); // Batalkan setelah 2 detik
🎯 Kapan Menggunakan AbortController?
- Pencarian Otomatis (Autocomplete): Batalkan request sebelumnya saat pengguna mengetik karakter baru.
- Navigasi Halaman: Batalkan request yang sedang berjalan saat pengguna berpindah halaman untuk menghindari race condition dan memori leak.
- Komponen yang Unmount: Batalkan request saat komponen React/Vue/dll. dilepas dari DOM.
b. Timeout: Mengatur Batas Waktu Request
Fetch API tidak memiliki opsi timeout bawaan. Namun, kita bisa mengimplementasikannya sendiri dengan AbortController dan Promise.race().
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout); // Batalkan setelah 'timeout' ms
return fetch(url, {
...options,
signal: controller.signal
}).finally(() => {
clearTimeout(id); // Bersihkan timer setelah request selesai/dibatalkan
});
}
async function fetchDataWithTimeout() {
try {
const response = await fetchWithTimeout('https://api.example.com/data-yang-lama', {}, 3000); // Timeout 3 detik
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}