Belajar dengan membaca secara berurutan

fetch — Mengambil Data dari API

Membahas fetch dan json() untuk mengambil JSON dari URL, response.ok untuk mendeteksi kegagalan, URL relatif, dan memuat dua file sekaligus dengan Promise.all.

Sejauh ini, data di latihan selalu ditulis langsung di dalam kode. Di situs sungguhan, harga dan stok bisa berubah, jadi setiap kali dibuka, halaman meminta data ke API milik server (URL yang dipakai program untuk mengambil data).

Artikel ini membahas fetch untuk mengambil data dari sebuah URL, dan response.ok untuk mengecek apakah request-nya berhasil.

Mengambil JSON dari API — fetch dan json()

Anggap kamu ingin halaman riwayat order menampilkan daftar order yang tersimpan di server. Order itu datang sebagai string JSON, jadi kamu perlu cara untuk menunggu sampai datanya tiba, lalu langkah untuk mengubah string itu kembali menjadi array. Di latihan, kamu akan mengambil file JSON dari folder fixtures di situs ini sebagai pengganti API sungguhan.

fetch (function yang meminta data di sebuah URL ke server) mengembalikan Promise yang fulfilled dengan Response, yaitu object yang mewakili jawaban server. status (angka yang bernilai 200 kalau berhasil) dan header (informasi tentang response yang dikirim sebelum body) datang lebih dulu, lalu body menyusul, jadi kamu perlu await sekali lagi pada response.json() untuk mendapatkan body-nya.

// Ambil JSON order yang disimpan di situs ini (cara menulis URL dibahas di bagian berikutnya)
const response = await fetch("fixtures/id/orders.json");
console.log(response.status);          // 200

// Baca seluruh body dan ubah dari JSON menjadi array
const orders = await response.json();
console.log(orders.length);            // 3
console.log(orders[1].total);          // 12600

// Setelah ini, orders adalah array biasa
const preparing = orders.filter((order) => order.status === "Diproses");
console.log(preparing.map((order) => order.id).join(", "));   // A-3002, A-3003
Header dan Body Tiba Terpisah, Jadi await Dua Kali
fetch(url)mengirim requestPromise masihpendingStatus 200 danheader tibaawait fetch(…)memberi ResponseSeluruh body tiba,diubah dari JSONawait json()memberi 3 order
await pada fetch selesai begitu header tiba. Array-nya baru ada setelah await pada json(), yang membaca body sampai habis.

json() membaca body sampai habis dan mengubahnya dengan cara yang sama seperti JSON.parse sebelum mengembalikan hasilnya, jadi hasil itu kamu tunggu dengan await. Setelah diubah, orders adalah array biasa, jadi filter dan map bisa langsung dipakai.

Response yang Di-log Tampil sebagai {}

Di console latihan, console.log(response) menampilkan {}, sama seperti element DOM. response.json() tanpa await adalah Promise, jadi hasilnya juga {}. Untuk mengecek Response, tampilkan status-nya; untuk mengecek body, tampilkan value yang kamu dapat dari await pada json().

Di halaman pengelolaan member, ambil daftar member dari server lalu tampilkan nama-namanya. Daftar member tersedia di fixtures/id/users.json.

① Ambil daftar member dan tampilkan status response-nya.

② Ubah body menjadi array dan tampilkan jumlah member.

③ Ambil namanya saja, gabungkan dengan “, ”, lalu tampilkan hasilnya.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Menulis URL Berdasarkan Lokasi Halaman — URL Relatif

Di bagian sebelumnya, cukup menulis "fixtures/id/orders.json" untuk mengambil file-nya. URL lengkap yang diawali https:// ikut memuat domain (bagian setelah https://, seperti runner.desktechlearn.com, yang menunjukkan situsnya), jadi URL itu harus kamu ubah setiap kali situsnya pindah.

URL relatif (URL yang ditulis berdasarkan URL halaman yang sedang dibuka) dicari mulai dari folder halaman itu kalau tidak diawali /, dan mulai dari root situs kalau diawali /. Kode latihan tidak berjalan di halaman artikel, tapi di runner.html, halaman tersembunyi di runner.desktechlearn.com.

// URL halaman tempat kode ini berjalan (console latihan)
// https://runner.desktechlearn.com/tools/js-editor/runner.html

// Tanpa / di depan: dicari mulai dari js-editor, folder tempat runner.html berada
const fromPage = await fetch("fixtures/id/orders.json");
console.log(fromPage.url);   // url adalah URL yang benar-benar diminta
// https://runner.desktechlearn.com/tools/js-editor/fixtures/id/orders.json

// Dengan / di depan: dicari mulai dari root situs
const fromRoot = await fetch("/tools/js-editor/fixtures/id/orders.json");
console.log(fromRoot.url === fromPage.url);   // true (keduanya menunjuk file yang sama)

// Kalau fixtures/id/ tidak ditulis, file dicari langsung di dalam js-editor
const noFolder = await fetch("orders.json");
console.log(noFolder.url);
// https://runner.desktechlearn.com/tools/js-editor/orders.json
Lokasi runner.html dan fixtures
runner.desktechlearn.com — root situs (/)
  • URL yang diawali / mulai dari sini
Folder /tools/js-editor/
  • runner.html — halaman tempat kode latihan berjalan
  • URL tanpa / di depan mulai dari sini
Folder fixtures/id
  • Berisi orders.json, users.json, dan products.json
fixtures ada di dalam js-editor, folder yang sama dengan runner.html. Ada atau tidaknya / di depan URL menentukan dari mana pencarian dimulai.

URL yang diawali / selalu menunjuk file yang sama, di folder mana pun halamannya berada. URL tanpa / tetap berfungsi kalau halaman dan fixtures dipindahkan bersama ke folder lain. Tabel di bawah menunjukkan titik awal tiap URL dan apakah file-nya ditemukan.

URLTitik awalFile ditemukan?
fixtures/id/orders.jsonjs-editorYa
/tools/js-editor/fixtures/id/orders.jsonRoot situsYa (file yang sama)
orders.jsonjs-editorTidak (tidak ada di js-editor)

Di DevTools, URL Dihitung dari Halaman Artikel

Kalau kamu membuka Console DevTools di halaman artikel lalu menjalankan fetch("fixtures/id/orders.json"), titik awalnya adalah URL halaman artikel, jadi request-nya tidak pernah sampai ke fixtures. Server halaman artikel mengembalikan HTML halaman dengan status 200 bahkan untuk path yang tidak ada, jadi fetch-nya bisa terlihat berhasil, padahal yang kamu terima bukan JSON.

Di halaman admin sebuah toko perlengkapan rumah, jumlahkan harga produk. brokenPath berisi URL daftar produk yang tidak menyertakan folder fixtures/id/.

① Ambil brokenPath dan tampilkan apakah status-nya 200 (true / false).

② Perbaiki URL-nya supaya dimulai dari folder halaman, ambil datanya, lalu tampilkan jumlah produk.

③ Ambil datanya dengan URL yang dimulai dari root situs, lalu tampilkan total harganya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Mendeteksi Request yang Gagal — response.ok dan status

Kalau URL-nya salah ketik atau file-nya sudah dihapus, server menjawab bahwa file itu tidak ditemukan. Jawabannya tetap datang, jadi fetch tetap fulfilled dengan Response. Walaupun ada di dalam try / catch, eksekusi tidak pindah ke catch, melainkan lanjut ke baris berikutnya yang membaca body.

response.ok bernilai true kalau status ada di antara 200 dan 299. Kode sukses bukan hanya 200 (201 juga termasuk), jadi pakai ok untuk menentukan apakah request-nya berhasil. Biasanya server mengembalikan 404 (Not Found) untuk file yang tidak ada, tapi server console latihan mengembalikan 403 (Forbidden).

// URL dengan salah ketik: orders ditulis order
const response = await fetch("fixtures/id/order.json");

// Walaupun file-nya tidak ditemukan, fetch tetap fulfilled dan mengembalikan Response
console.log(response.ok);       // false
console.log(response.status);   // 404 atau 403 (tergantung server; console latihan mengembalikan 403)

// Ubah body menjadi array hanya kalau ok bernilai true
if (response.ok) {
  const orders = await response.json();
  console.log(`Jumlah order: ${orders.length}`);
} else {
  console.log("Gagal memuat daftar order");   // Gagal memuat daftar order
}
Tiga Kemungkinan Hasil Pengecekan ok
await fetch(url)tetap fulfilledorders.jsonstatus 200order.jsonstatus 403dan sejenisnyaorder.jsonstatus 403dan sejenisnyaok bernilai truepanggil json()ok bernilai falselewati json()ok tidak dicek,json() dipanggildapat arrayberisi 3 ordertampilkan pesankesalahanSyntaxErrorbody bukan JSON
fetch tetap fulfilled walaupun file-nya tidak ada. Memanggil json() pada body yang bukan JSON akan melempar SyntaxError.

Di kolom kanan, body-nya berisi penjelasan error, bukan JSON (di console latihan bentuknya XML), jadi json() melempar SyntaxError di baris itu. Kolom tengah mengecek ok lebih dulu dan menampilkan pesan tanpa membaca body, jadi request yang gagal berakhir dengan pesan di layar, bukan error.

Muat daftar produk dan ulasan untuk ditampilkan di halaman produk. Ulasannya belum dipublikasikan.

① Ambil fixtures/id/products.json dan tampilkan apakah berhasil (true / false).

② Ambil fixtures/id/reviews.json dan tampilkan apakah berhasil.

③ Kalau ② gagal, tampilkan “Belum ada ulasan”; kalau berhasil, tampilkan jumlahnya dalam bentuk “5 ulasan”.

④ Kalau ① berhasil, ubah body menjadi array dan tampilkan nama produk yang stoknya 0.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Mengambil Dua File Sekaligus — async function dan Promise.all

Halaman admin yang menampilkan jumlah member dan jumlah order berdampingan butuh dua file JSON. Keduanya tidak saling bergantung, jadi kamu memuatnya secara paralel (memulai beberapa proses sekaligus, lalu menunggu semuanya), seperti di artikel async / await. Tapi kalau kamu memanggil fetch dua kali lalu memberikan hasilnya ke Promise.all, yang kamu dapat adalah dua Response, bukan body-nya.

Kalau semua langkah dari fetch sampai json() kamu bungkus dalam satu async function, Promise yang dikembalikan saat function itu dipanggil akan fulfilled dengan isi body-nya. Kalau function itu dipanggil dua kali lalu ditunggu dengan Promise.all, kamu mendapat array berisi isi body sesuai urutan yang kamu berikan.

// Bungkus fetch sampai json() dan kembalikan isi body-nya (null kalau tidak ditemukan)
async function loadJson(path) {
  const response = await fetch(path);
  if (!response.ok) {
    return null;
  }
  return await response.json();
}

// Kalau yang diberikan 2 panggilan fetch, hasilnya Response
const responses = await Promise.all([
  fetch("fixtures/id/products.json"),
  fetch("fixtures/id/orders.json"),
]);
console.log(responses[0].length);   // undefined (bukan array)

// Memanggil loadJson 2 kali memulai keduanya sekaligus, dan Promise.all mengumpulkan array body-nya
const [products, orders] = await Promise.all([
  loadJson("fixtures/id/products.json"),
  loadJson("fixtures/id/orders.json"),
]);
console.log(`${products.length} produk / ${orders.length} order`);   // 4 produk / 3 order
Hasil fetch Tidak Berisi Body
Beri Promise.all2 hasil fetch()Selesai saat keduaResponse tibaHasilnya2 Responseresponses[0].lengthundefinedBeri Promise.all2 hasil loadJson()Tiap panggilanmenunggu json()Hasilnya2 arrayproducts.lengthbernilai 4
Baris atas selesai begitu Response-nya tiba. Untuk mengumpulkan body, berikan hasil dari function yang menunggu sampai json().

Kalau satu saja Promise yang kamu berikan rejected, Promise.all gagal tanpa memberikan hasil lainnya. loadJson fulfilled dengan null saat ok bernilai false, jadi walaupun satu file tidak ada, kamu tetap mendapat array yang lain.

Di halaman rekap penjualan, muat member, order, dan ulasan sekaligus. Array path paths sudah dideklarasikan, dan ulasannya belum dipublikasikan.

① Buat async function loadList yang mengembalikan isi body kalau berhasil dan array kosong kalau gagal.

② Jalankan setiap path di paths lewat loadList, tunggu semuanya sekaligus, lalu tampilkan baris seperti “4 member / 3 order / 0 ulasan”.

③ Pakai userId untuk mencari member yang membuat order pertama, lalu tampilkan namanya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Setelah const response = await fetch(url); dijalankan, apa isi response?

Soal 2Dengan path yang tidak ada, bagaimana kondisinya tepat setelah const res = await fetch(url); dijalankan?

Soal 3Di kode yang berjalan di /tools/js-editor/runner.html, di mana fetch("users.json") mencari file-nya?