JavaScript · Bab 6
JSON & fetch: Mengambil Data dari API
Format JSON, JSON.parse dan JSON.stringify, Promise, fetch dengan async/await, status HTTP dan bentuk galat, keadaan memuat/kosong/galat, query string, serta mengirim data JSON ke API toko Smart Retail.
Sampai Bab 5, data toko ditulis langsung di dalam kode: array produk, teks harga, daftar tugas. Di aplikasi sungguhan, data itu disimpan di server dan halaman memintanya lewat API (Application Programming Interface): alamat URL yang menjawab dengan data, bukan dengan halaman HTML. Bab ini mengajarkan dua alat untuk itu, yaitu format JSON dan fungsi fetch.
Bab ini juga jembatan ke tingkat berikutnya. Di materi Laravel API, kamu membuat endpoint GET /api/v1/products; di topik frontend terpisah (Vue), halaman memakai endpoint yang sama lewat axios. Semua yang dibahas di sini (JSON, fetch, async/await, status HTTP) tetap berlaku di sana.
6.1 JSON: Format Teks untuk Data
JSON (JavaScript Object Notation) adalah format teks untuk bertukar data. Bentuknya mirip objek dan array JavaScript, tetapi aturannya lebih ketat:
- nama properti wajib diapit tanda kutip ganda:
"name", bukanname; - teks wajib memakai kutip ganda, bukan kutip tunggal;
- nilai yang boleh: teks, angka,
true/false,null, array, dan objek. Fungsi,undefined, dan komentar tidak boleh; - tidak boleh ada koma setelah elemen terakhir.
Satu produk dari API Smart Retail berbentuk seperti ini:
{
"id": 3,
"product_code": "DR-002",
"name": "Air Mineral",
"price": 3000,
"stock": 40,
"category": { "id": 2, "name": "Cold Beverages" }
}
Data yang dikirim lewat jaringan selalu berupa teks. Karena itu ada dua fungsi bawaan:
JSON.parse(teks)mengubah teks JSON menjadi objek JavaScript;JSON.stringify(nilai)mengubah objek atau array menjadi teks JSON.
🧪 Coba ubah: tambahkan properti catatan: undefined ke salah satu isi keranjang, lalu perhatikan bahwa JSON.stringify membuangnya.
Belum ada output.
JSON.stringify(nilai, null, 2) menambahkan indentasi dua spasi sehingga teksnya mudah dibaca. Teks JSON yang tidak sah membuat JSON.parse melempar SyntaxError, jadi data dari luar sebaiknya dibaca di dalam try...catch.
6.2 Kode yang Menunggu: Promise
Kode di bab sebelumnya berjalan sinkron: baris kedua menunggu baris pertama selesai. Meminta data ke server butuh waktu (milidetik sampai detik), dan halaman tidak boleh membeku selama menunggu. Karena itu fetch bekerja asinkron: ia langsung mengembalikan sebuah Promise, yaitu janji bahwa hasilnya akan tersedia nanti.
Sebuah Promise berada di salah satu dari tiga keadaan:
| Keadaan | Arti |
|---|---|
pending | masih menunggu |
fulfilled | berhasil, nilainya tersedia |
rejected | gagal, ada alasan galatnya |
Promise yang berhasil dibaca dengan .then(...), dan yang gagal ditangkap dengan .catch(...). Contoh berikut memakai setTimeout untuk meniru pekerjaan yang butuh waktu:
🧪 Coba ubah: ganti "Pulpen Biru" di pemanggilan cekStok menjadi "Penghapus", lalu lihat jalur .catch.
Belum ada output.
Urutan output 1, 2, lalu 3 menunjukkan intinya: kode setelah Promise tetap berjalan, dan hasilnya datang belakangan. Di praktik, kamu jarang membuat Promise sendiri. Yang lebih sering adalah memakai Promise yang dikembalikan fetch.
6.3 fetch dengan async dan await
fetch(url, opsi) mengirim permintaan HTTP dan mengembalikan Promise berisi objek Response. Daripada merangkai .then, kode modern memakai dua kata kunci:
asyncdi depan fungsi: fungsi itu selalu mengembalikan Promise dan boleh memakaiawaitdi dalamnya;awaitdi depan Promise: tunggu sampai Promise selesai, lalu ambil nilainya. Kalau Promise gagal,awaitmelempar galat yang bisa ditangkaptry...catch.
await hanya boleh dipakai di dalam fungsi async. Di <script> biasa, await di luar fungsi adalah syntax error (di <script type="module"> boleh).
Objek Response punya beberapa bagian penting:
| Bagian | Isi |
|---|---|
response.status | kode status HTTP, misalnya 200 atau 404 |
response.ok | true bila status 200 sampai 299 |
response.headers.get("content-type") | jenis isi jawaban, misalnya application/json |
response.json() | membaca isi jawaban sebagai JSON. Ini juga Promise, jadi perlu await |
🧪 Coba ubah: hapus await sebelum response.json(), lalu lihat apa yang tercetak sebagai hasil.data.
Belum ada output.
Header Accept: "application/json" memberi tahu server bahwa halaman ini mengharapkan jawaban JSON. Server Laravel memakai header ini untuk memutuskan bentuk jawaban galat, jadi biasakan selalu mengirimnya.
6.4 Status HTTP dan Bentuk Galat
API Smart Retail menjawab dengan kode status dan isi JSON yang tetap. Bentuk di bawah ini sama dengan yang dihasilkan API Laravel di materi Laravel API, asalkan permintaan membawa header Accept: application/json. Tanpa header itu, Laravel bisa menjawab galat dengan redirect atau halaman HTML, bukan JSON.
| Status | Arti | Contoh isi |
|---|---|---|
200 | berhasil | { "data": [ ... ], "links": { ... }, "meta": { ... } } |
201 | data baru dibuat | { "data": { "id": 8, ... } } |
204 | berhasil, tanpa isi | (kosong, jangan panggil json()) |
401 | belum login atau token salah | { "message": "Unauthenticated." } |
403 | login, tetapi tidak berhak | { "message": "This action is unauthorized." } |
404 | data atau rute tidak ada | { "message": "No query results for model [App\\Models\\Product] 99" } |
422 | data yang dikirim tidak valid | { "message": "...", "errors": { "name": ["The name field is required."] } } |
500 | galat di server | { "message": "Server Error" } |
Pola yang aman: baca status dulu, baru putuskan cara membaca isinya.
🧪 Coba ubah: ganti "/products/3" menjadi "/categories/3", lalu lihat bahwa isinya punya bentuk yang berbeda.
Belum ada output.
Pesan di message ditulis untuk pengembang, bukan untuk pembeli. Di tampilan, ubah status menjadi kalimat yang ramah, misalnya 404 menjadi “Data tidak ditemukan”, dan simpan pesan aslinya di console untuk diperiksa.
6.5 Memuat, Kosong, dan Galat
Halaman yang mengambil data selalu berada di salah satu dari empat keadaan, dan setiap keadaan butuh tampilan sendiri:
- memuat: permintaan belum selesai. Tampilkan teks “Memuat…” dan nonaktifkan tombol agar tidak diklik dua kali;
- berhasil: tampilkan datanya;
- kosong: berhasil, tetapi datanya nol. Tampilkan kalimat yang menjelaskan, bukan daftar kosong tanpa keterangan;
- galat: status tidak ok (misalnya 404 dengan header
Accept: application/json) atau jaringan gagal. Tampilkan pesan dan biarkan pengguna mencoba lagi.
Di playground berikut, server tiruan sengaja memberi jeda sekitar 400 milidetik saat Jalankan agar keadaan memuat sempat terlihat. Atribut aria-live="polite" membuat pembaca layar ikut mengumumkan perubahan teks status.
🧪 Coba ubah: ganti API menjadi "https://api.contoh.test/api/v1", jalankan lagi, lalu lihat keadaan galat jaringan.
Belum ada output.
Blok finally selalu berjalan, baik permintaan berhasil maupun gagal, sehingga tombol pasti aktif kembali.
6.6 Query String dengan URLSearchParams
Pencarian, urutan, dan halaman dikirim lewat query string, bagian URL setelah tanda ?, misalnya /products?search=teh&page=2. Menyusunnya dengan menyambung teks mudah salah, terutama bila kata kunci memuat spasi atau &. URLSearchParams menyusun dan meng-encode-nya dengan benar:
🧪 Coba ubah: tambahkan params.delete("sort") sebelum mencetak, lalu bandingkan hasilnya.
Belum ada output.
API Smart Retail mengenal tiga parameter untuk GET /api/v1/products: search (nama memuat kata itu, tanpa membedakan huruf besar dan kecil), sort (name, price, atau stock; tanda - di depan berarti menurun), dan page. Satu halaman berisi lima produk. Informasi halaman ada di objek meta:
🧪 Coba ubah: panggil produkTermahal(2), lalu lihat bahwa links.next menjadi null di halaman terakhir.
Belum ada output.
6.7 Mengirim Data: POST, Header, dan Token
fetch juga bisa mengirim data. Untuk membuat produk baru, opsi kedua fetch diisi:
method: "POST";headers:Accept: "application/json"agar galat dijawab sebagai JSON;"Content-Type": "application/json"agar server tahu isi body adalah JSON. Laravel sungguhan membaca body JSON hanya bila header ini ada;Authorization: "Bearer <token>"untuk endpoint yang butuh login. Token adalah teks rahasia yang didapat dariPOST /api/logindan membuktikan siapa pengirimnya;
body: JSON.stringify(data), karena body harus berupa teks.
Contoh berikut login sebagai admin, lalu mencoba membuat produk tiga kali: tanpa token (401), dengan data kosong (422), dan dengan data lengkap (201).
🧪 Coba ubah: kirim price: -1 pada produkBaru, lalu baca isi errors.price dari jawaban 422.
Belum ada output.
Isi errors pada 422 adalah objek dengan nama field sebagai kunci dan array pesan sebagai nilai. Bentuk ini memudahkan menampilkan pesan tepat di bawah input yang salah. Jawaban 401 dan 422 di atas berbentuk JSON karena permintaan membawa Accept: application/json.
6.8 Yang Ditiru dan yang Berbeda di Dunia Nyata
Server tiruan dibuat sedekat mungkin dengan API Laravel (bentuk data, paginasi lima produk, pesan 401/404/422), tetapi tetap tiruan. Bedanya dengan server sungguhan:
- Tanpa jaringan. Hanya
http://127.0.0.1:8000/apiyang dijawab. Alamat lain, termasuk alamat relatif seperti"/api/v1/products", ditolak denganTypeError: Failed to fetch, sama seperti server yang tidak hidup. - Data kembali ke awal setiap Jalankan atau Cek. Produk yang kamu buat tidak tersimpan.
- Jeda dibuat-buat: sekitar 400 milidetik saat Jalankan, nol saat Cek. Di internet, jeda bisa jauh lebih lama atau tidak menentu.
- Header tidak diperiksa. Server tiruan tetap membaca body JSON dan menjawab galat sebagai JSON walau
Content-TypeatauAccept: application/jsonlupa dikirim. Laravel sungguhan tidak seramah itu, jadi tetap kirim keduanya. - Tanpa CORS. Browser hanya mengizinkan halaman membaca jawaban dari origin lain bila server API mengizinkannya (CORS). Pengaturannya ada di sisi Laravel dan dibahas bersama topik Vue.
Kalau halamanmu dipublikasikan ke hosting statis, alamat 127.0.0.1:8000 tidak ada di internet. Ganti dengan URL API sungguhan, atau dengan berkas JSON statis yang ikut dipublikasikan, misalnya fetch("produk.json"), yang bentuk isinya sama dengan jawaban API.