Mengambil Data: Loading, Error & Async
Pola baku mengambil data asinkron: state data/loading/error, async/await dengan try/catch/finally, fetch, race condition, dan membungkusnya sebagai composable.
Hampir semua aplikasi butuh data dari server
Daftar produk, profil pengguna, cuaca — semuanya datang lewat jaringan, yang tidak instan dan bisa gagal. Tampilan yang baik harus menangani tiga keadaan: sedang memuat, berhasil, dan gagal. Mengabaikannya membuat pengguna melihat layar kosong tanpa tahu apa yang terjadi.
Pola tiga state
<script setup>
import { ref, onMounted } from 'vue'
const data = ref([])
const loading = ref(false)
const error = ref('')
async function muat() {
loading.value = true
error.value = ''
try {
const res = await fetch('https://api.contoh.com/produk')
if (!res.ok) throw new Error('HTTP ' + res.status) // fetch tidak otomatis melempar error utk 404/500!
data.value = await res.json()
} catch (e) {
error.value = 'Gagal memuat: ' + e.message
} finally {
loading.value = false // selalu jalan, sukses maupun gagal
}
}
onMounted(muat)
</script>
<template>
<p v-if="loading">Memuat...</p>
<p v-else-if="error">{{ error }} <button @click="muat">Coba lagi</button></p>
<ul v-else>
<li v-for="p in data" :key="p.id">{{ p.nama }}</li>
</ul>
</template>
| Bagian | Peran |
|---|---|
try | Kode yang mungkin gagal |
catch | Menangani kegagalan (isi pesan error) |
finally | Dijalankan selalu — tempat mematikan loading |
async / await dalam sekejap
async functionselalu mengembalikan Promise.await"menunggu" Promise selesai, tanpa memblokir halaman.- Kegagalan Promise menjadi exception yang bisa ditangkap
try/catch.
Beberapa permintaan sekaligus
const [produk, kategori] = await Promise.all([
fetch('/api/produk').then(r => r.json()),
fetch('/api/kategori').then(r => r.json()),
])
Mengirim data (POST)
await fetch('/api/tugas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ judul: 'Belajar Vue' }),
})
Race condition: respons yang datang terlambat
Jika pengguna mengetik cepat, permintaan lama bisa selesai setelah permintaan baru dan menimpa hasil yang benar. Gunakan onCleanup pada watch atau AbortController:
watch(kataKunci, async (baru, _, onCleanup) => {
const ctl = new AbortController()
onCleanup(() => ctl.abort()) // batalkan permintaan usang
const res = await fetch('/api/cari?q=' + baru, { signal: ctl.signal })
hasil.value = await res.json()
})
Bungkus jadi composable
// useFetch.js
export function useFetch(url) {
const data = ref(null), error = ref(null), loading = ref(true)
fetch(url).then(r => r.json()).then(d => (data.value = d))
.catch(e => (error.value = e)).finally(() => (loading.value = false))
return { data, error, loading }
}
Untuk kebutuhan lanjut (cache, retry, refetch otomatis) pertimbangkan pustaka seperti TanStack Query atau VueUse useFetch.
Di latihan ini kita tidak memanggil jaringan sungguhan; fungsi ambilData() mensimulasikan API dengan setTimeout agar hasilnya bisa dicek otomatis. Logika loading/error/data-nya sama persis dengan yang dipakai dengan fetch.
Kesalahan umum
- Lupa mematikan
loadingsaat gagal (taruh difinally). - Mengira
fetchmelempar error untuk status 404/500 — periksares.ok. - Tidak mereset
errorsebelum permintaan baru sehingga pesan lama tetap tampil.