Fetch API

Codelab ini adalah bagian dari kursus pelatihan Developing Progressive Web Apps, yang dikembangkan oleh tim Pelatihan Google Developers. Anda akan mendapatkan manfaat maksimal dari kursus ini jika menyelesaikan codelab secara berurutan.

Untuk mengetahui detail lengkap tentang kursus ini, lihat Ringkasan Pengembangan Progressive Web App.

Pengantar

Lab ini memandu Anda menggunakan Fetch API, antarmuka sederhana untuk mengambil resource, dan peningkatan dari XMLHttpRequest API.

Yang akan Anda pelajari

  • Cara menggunakan Fetch API untuk meminta resource
  • Cara membuat permintaan GET, HEAD, dan POST dengan pengambilan
  • Cara membaca & menyetel header kustom
  • Penggunaan dan batasan CORS

Yang perlu Anda ketahui

  • JavaScript dan HTML dasar
  • Memahami konsep dan sintaksis dasar Promises ES2015

Yang akan Anda butuhkan

  • Komputer dengan akses terminal/shell
  • Koneksi ke internet
  • Browser yang mendukung Fetch
  • Editor teks
  • Node dan npm

Catatan: Meskipun Fetch API saat ini tidak didukung di semua browser, ada polyfill.

Download atau clone repositori pwa-training-labs dari github dan instal Node.js versi LTS, jika diperlukan.

Buka command line komputer Anda. Buka direktori fetch-api-lab/app/ dan mulai server pengembangan lokal:

cd fetch-api-lab/app
npm install
node server.js

Anda dapat menghentikan server kapan saja dengan Ctrl-c.

Buka browser Anda, lalu buka localhost:8081/. Anda akan melihat halaman dengan tombol untuk membuat permintaan (tombol tersebut belum berfungsi).

Catatan: Batalkan pendaftaran pekerja layanan dan hapus semua cache pekerja layanan untuk localhost agar tidak mengganggu lab. Di Chrome DevTools, Anda dapat melakukannya dengan mengklik Hapus data situs dari bagian Hapus penyimpanan di tab Aplikasi.

Buka folder fetch-api-lab/app/ di editor teks pilihan Anda. Folder app/ adalah tempat Anda akan membangun lab.

Folder ini berisi:

  • echo-servers/ berisi file yang digunakan untuk menjalankan server pengujian
  • examples/ berisi contoh resource yang kita gunakan dalam bereksperimen dengan pengambilan
  • js/main.js adalah JavaScript utama untuk aplikasi, dan di sinilah Anda akan menulis semua kode
  • index.html adalah halaman HTML utama untuk situs/aplikasi contoh kami
  • package-lock.json dan package.json adalah file konfigurasi untuk dependensi server pengembangan dan server echo kami
  • server.js adalah server pengembangan node

Fetch API memiliki antarmuka yang relatif sederhana. Bagian ini menjelaskan cara menulis permintaan HTTP dasar menggunakan fetch.

Mengambil file JSON

Di js/main.js, tombol Fetch JSON aplikasi dilampirkan ke fungsi fetchJSON.

Perbarui fungsi fetchJSON untuk meminta file examples/animals.json dan mencatat respons:

function fetchJSON() {
  fetch('examples/animals.json')
    .then(logResult)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Fetch JSON. Konsol akan mencatat respons pengambilan data.

Penjelasan

Metode fetch menerima jalur untuk resource yang ingin kita ambil sebagai parameter, dalam hal ini examples/animals.json. fetch menampilkan promise yang diselesaikan ke objek Respons. Jika promise diselesaikan, respons akan diteruskan ke fungsi logResult. Jika promise ditolak, catch akan mengambil alih dan error akan diteruskan ke fungsi logError.

Objek respons merepresentasikan respons terhadap permintaan. Objek ini berisi isi respons serta properti dan metode yang berguna.

Menguji respons tidak valid

Periksa respons yang dicatat ke dalam log di konsol. Perhatikan nilai properti status, url, dan ok.

Ganti resource examples/animals.json di fetchJSON dengan examples/non-existent.json. Fungsi fetchJSON yang diupdate kini akan terlihat seperti:

function fetchJSON() {
  fetch('examples/non-existent.json')
    .then(logResult)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Fetch JSON lagi untuk mencoba mengambil resource yang tidak ada ini.

Perhatikan bahwa pengambilan berhasil diselesaikan, dan tidak memicu pemblokiran catch. Sekarang temukan properti status, URL, dan ok dari respons baru.

Nilai untuk kedua file tersebut harus berbeda (apakah Anda mengerti alasannya?). Jika Anda mendapatkan error konsol, apakah nilai tersebut cocok dengan konteks error?

Penjelasan

Mengapa respons yang gagal tidak mengaktifkan blok catch? Ini adalah catatan penting untuk pengambilan dan janji—respons buruk (seperti 404) tetap diselesaikan. Fetch promise hanya ditolak jika permintaan tidak dapat diselesaikan, jadi Anda harus selalu memeriksa validitas respons. Kita akan memvalidasi respons di bagian berikutnya.

Untuk informasi selengkapnya

Memeriksa validitas respons

Kita perlu memperbarui kode untuk memeriksa validitas respons.

Di main.js, tambahkan fungsi untuk memvalidasi respons:

function validateResponse(response) {
  if (!response.ok) {
    throw Error(response.statusText);
  }
  return response;
}

Kemudian, ganti fetchJSON dengan kode berikut:

function fetchJSON() {
  fetch('examples/non-existent.json')
    .then(validateResponse)
    .then(logResult)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Fetch JSON. Periksa konsol. Sekarang, respons untuk examples/non-existent.json akan memicu blok catch.

Ganti examples/non-existent.json di fungsi fetchJSON dengan examples/animals.json asli. Fungsi yang diupdate kini akan terlihat seperti:

function fetchJSON() {
  fetch('examples/animals.json')
    .then(validateResponse)
    .then(logResult)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Fetch JSON. Anda akan melihat bahwa respons berhasil dicatat seperti sebelumnya.

Penjelasan

Setelah menambahkan pemeriksaan validateResponse, respons buruk (seperti 404) akan memunculkan error dan catch akan mengambil alih. Hal ini memungkinkan kami menangani respons yang gagal dan mencegah respons yang tidak terduga menyebar ke bawah rantai pengambilan data.

Baca responsnya

Respons pengambilan data ditampilkan sebagai ReadableStreams (spesifikasi stream) dan harus dibaca untuk mengakses isi respons. Objek respons memiliki metode untuk melakukannya.

Di main.js, tambahkan fungsi readResponseAsJSON dengan kode berikut:

function readResponseAsJSON(response) {
  return response.json();
}

Kemudian, ganti fungsi fetchJSON dengan kode berikut:

function fetchJSON() {
  fetch('examples/animals.json') // 1
  .then(validateResponse) // 2
  .then(readResponseAsJSON) // 3
  .then(logResult) // 4
  .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Fetch JSON. Periksa konsol untuk melihat bahwa JSON dari examples/animals.json sedang dicatat (bukan objek Respons).

Penjelasan

Mari kita tinjau apa yang terjadi.

Langkah 1. Pengambilan dipanggil pada resource, examples/animals.json. Fetch menampilkan promise yang diselesaikan ke objek Respons. Saat promise di-resolve, objek respons diteruskan ke validateResponse.

Langkah 2. validateResponse memeriksa apakah respons valid (apakah responsnya 200?). Jika tidak, error akan ditampilkan, melewati blok then lainnya dan memicu blok catch. Hal ini sangat penting. Tanpa pemeriksaan ini, respons buruk akan diteruskan ke bawah dan dapat merusak kode selanjutnya yang mungkin mengandalkan penerimaan respons yang valid. Jika respons valid, respons tersebut diteruskan ke readResponseAsJSON.

Langkah 3. readResponseAsJSON membaca isi respons menggunakan metode Response.json(). Metode ini menampilkan promise yang diselesaikan ke JSON. Setelah promise ini diselesaikan, data JSON akan diteruskan ke logResult. (Jika promise dari response.json() ditolak, blok catch akan dipicu.)

Langkah 4. Terakhir, data JSON dari permintaan asli ke examples/animals.json dicatat oleh logResult.

Untuk informasi selengkapnya

Pengambilan data tidak terbatas pada JSON. Dalam contoh ini, kita akan mengambil gambar dan menambahkannya ke halaman.

Di main.js, tulis fungsi showImage dengan kode berikut:

function showImage(responseAsBlob) {
  const container = document.getElementById('img-container');
  const imgElem = document.createElement('img');
  container.appendChild(imgElem);
  const imgUrl = URL.createObjectURL(responseAsBlob);
  imgElem.src = imgUrl;
}

Kemudian, tambahkan fungsi readResponseAsBlob yang membaca respons sebagai Blob:

function readResponseAsBlob(response) {
  return response.blob();
}

Perbarui fungsi fetchImage dengan kode berikut:

function fetchImage() {
  fetch('examples/fetching.jpg')
    .then(validateResponse)
    .then(readResponseAsBlob)
    .then(showImage)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Ambil gambar. Anda akan melihat lucu mengambil tongkat di halaman (ini adalah lelucon mengambil).

Penjelasan

Dalam contoh ini, gambar sedang diambil, examples/fetching.jpg. Seperti pada latihan sebelumnya, respons divalidasi dengan validateResponse. Respons kemudian dibaca sebagai Blob (bukan JSON seperti di bagian sebelumnya). Elemen gambar dibuat dan ditambahkan ke halaman, dan atribut src gambar ditetapkan ke URL data yang merepresentasikan Blob.

Catatan: Metode createObjectURL() objek URL digunakan untuk membuat URL data yang merepresentasikan Blob. Hal ini penting untuk diperhatikan. Anda tidak dapat menyetel sumber gambar langsung ke Blob. Blob harus dikonversi menjadi URL data.

Untuk informasi selengkapnya

Bagian ini adalah tantangan opsional.

Update fungsi fetchText menjadi

  1. ambil /examples/words.txt
  2. memvalidasi respons dengan validateResponse
  3. membaca respons sebagai teks (petunjuk: lihat Response.text())
  4. dan menampilkan teks di halaman

Anda dapat menggunakan fungsi showText ini sebagai helper untuk menampilkan teks akhir:

function showText(responseAsText) {
  const message = document.getElementById('message');
  message.textContent = responseAsText;
}

Simpan skrip dan muat ulang halaman. Klik Ambil teks. Jika telah menerapkan fetchText dengan benar, Anda akan melihat teks yang ditambahkan di halaman.

Catatan: Meskipun Anda mungkin tergoda untuk mengambil HTML dan menambahkannya menggunakan atribut innerHTML, berhati-hatilah. Hal ini dapat membuat situs Anda rentan terhadap serangan pembuatan skrip lintas situs.

Untuk informasi selengkapnya

Secara default, pengambilan menggunakan metode GET, yang mengambil resource tertentu. Namun, pengambilan data juga dapat menggunakan metode HTTP lainnya.

Membuat permintaan HEAD

Ganti fungsi headRequest dengan kode berikut:

function headRequest() {
  fetch('examples/words.txt', {
    method: 'HEAD'
  })
  .then(validateResponse)
  .then(readResponseAsText)
  .then(logResult)
  .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik Permintaan HEAD. Perhatikan bahwa konten teks yang dicatat kosong.

Penjelasan

Metode fetch dapat menerima parameter opsional kedua, init. Parameter ini memungkinkan konfigurasi permintaan pengambilan, seperti metode permintaan, mode cache, kredensial, dan lainnya.

Dalam contoh ini, kita menetapkan metode permintaan pengambilan ke HEAD menggunakan parameter init. Permintaan HEAD sama seperti permintaan GET, kecuali isi responsnya kosong. Jenis permintaan ini dapat digunakan saat Anda hanya menginginkan metadata tentang file, tetapi tidak perlu mentransfer semua data file.

Opsional: Temukan ukuran resource

Mari kita lihat Header respons pengambilan untuk examples/words.txt guna menentukan ukuran file.

Perbarui fungsi headRequest untuk mencatat properti content-length dari respons headers (petunjuk: lihat dokumentasi header dan metode get).

Setelah memperbarui kode, simpan file dan muat ulang halaman. Klik Permintaan HEAD. Konsol harus mencatat ukuran (dalam byte) examples/words.txt.

Penjelasan

Dalam contoh ini, metode HEAD digunakan untuk meminta ukuran (dalam byte) resource (yang ditampilkan di header content-length) tanpa benar-benar memuat resource itu sendiri. Dalam praktiknya, hal ini dapat digunakan untuk menentukan apakah resource lengkap harus diminta (atau bahkan cara memintanya).

Opsional: Cari tahu ukuran examples/words.txt menggunakan metode lain dan pastikan ukurannya cocok dengan nilai dari header respons (Anda dapat mencari cara melakukannya untuk sistem operasi tertentu—poin bonus untuk menggunakan command line).

Untuk informasi selengkapnya

Fetch juga dapat mengirim data dengan permintaan POST.

Menyiapkan server echo

Untuk contoh ini, Anda perlu menjalankan server echo. Dari direktori fetch-api-lab/app/, jalankan perintah berikut (jika command line Anda diblokir oleh server localhost:8081, buka jendela atau tab command line baru):

node echo-servers/cors-server.js

Perintah ini memulai server sederhana di localhost:5000/ yang mengembalikan permintaan yang dikirimkan kepadanya.

Anda dapat menghentikan server ini kapan saja dengan ctrl+c.

Buat permintaan POST

Ganti fungsi postRequest dengan kode berikut (pastikan Anda telah menentukan fungsi showText dari bagian 4 jika Anda belum menyelesaikan bagian tersebut):

function postRequest() {
  fetch('http://localhost:5000/', {
    method: 'POST',
    body: 'name=david&message=hello'
  })
    .then(validateResponse)
    .then(readResponseAsText)
    .then(showText)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Klik POST request. Amati permintaan yang dikirimkan dan ditampilkan di halaman. Objek ini harus berisi nama dan pesan (perhatikan bahwa kita belum mendapatkan data dari formulir).

Penjelasan

Untuk membuat permintaan POST dengan pengambilan, kita menggunakan parameter init untuk menentukan metode (mirip dengan cara kita menetapkan metode HEAD di bagian sebelumnya). Di sini juga kita menetapkan body permintaan, dalam hal ini berupa string sederhana. Isinya adalah data yang ingin kita kirim.

Catatan: Dalam produksi, jangan lupa untuk selalu mengenkripsi data pengguna yang sensitif.

Saat data dikirim sebagai permintaan POST ke localhost:5000/, permintaan akan dikirim kembali sebagai respons. Respons kemudian divalidasi dengan validateResponse, dibaca sebagai teks, dan ditampilkan di halaman.

Pada praktiknya, server ini akan merepresentasikan API pihak ketiga.

Opsional: Menggunakan antarmuka FormData

Anda dapat menggunakan antarmuka FormData untuk mengambil data dari formulir dengan mudah.

Dalam fungsi postRequest, buat instance objek FormData baru dari elemen formulir msg-form:

const formData = new FormData(document.getElementById('msg-form'));

Kemudian, ganti nilai parameter body dengan variabel formData.

Simpan skrip dan muat ulang halaman. Isi formulir (bidang Nama dan Pesan) di halaman, lalu klik permintaan POST. Amati konten formulir yang ditampilkan di halaman.

Penjelasan

Konstruktor FormData dapat menerima form HTML, dan membuat objek FormData. Objek ini diisi dengan kunci dan nilai formulir.

Untuk informasi selengkapnya

Mulai server echo non-cors

Hentikan server echo sebelumnya (dengan menekan ctrl+c dari command line) dan mulai server echo baru dari direktori fetch-lab-api/app/ dengan menjalankan perintah berikut:

node echo-servers/no-cors-server.js

Perintah ini menyiapkan server echo sederhana lainnya, kali ini di localhost:5001/. Namun, server ini tidak dikonfigurasi untuk menerima permintaan lintas asal.

Mengambil dari server baru

Sekarang setelah server baru berjalan di localhost:5001/, kita dapat mengirim permintaan pengambilan ke server tersebut.

Perbarui fungsi postRequest untuk mengambil data dari localhost:5001/, bukan localhost:5000/. Setelah Anda memperbarui kode, simpan file, muat ulang halaman, lalu klik POST Request.

Anda akan mendapatkan error di konsol yang menunjukkan bahwa permintaan lintas origin diblokir karena header Access-Control-Allow-Origin CORS tidak ada.

Perbarui fetch dalam fungsi postRequest dengan kode berikut, yang menggunakan mode no-cors (seperti yang disarankan oleh log error), dan menghapus panggilan ke validateResponse dan readResponseAsText (lihat penjelasan di bawah):

function postRequest() {
  const formData = new FormData(document.getElementById('msg-form'));
  fetch('http://localhost:5001/', {
    method: 'POST',
    body: formData,
    mode: 'no-cors'
  })
    .then(logResult)
    .catch(logError);
}

Simpan skrip dan muat ulang halaman. Kemudian, isi formulir pesan dan klik POST Request.

Amati objek respons yang dicatat di konsol.

Penjelasan

Fetch (dan XMLHttpRequest) mengikuti kebijakan origin yang sama. Artinya, browser membatasi permintaan HTTP lintas origin dari dalam skrip. Permintaan lintas origin terjadi saat satu domain (misalnya http://foo.com/) meminta resource dari domain terpisah (misalnya http://bar.com/).

Catatan: Pembatasan permintaan lintas origin sering kali menimbulkan kebingungan. Banyak resource seperti gambar, stylesheet, dan skrip diambil di seluruh domain (yaitu, lintas origin). Namun, ini adalah pengecualian untuk kebijakan origin yang sama. Permintaan lintas origin masih dibatasi dari dalam skrip.

Karena server aplikasi kita memiliki nomor port yang berbeda dengan kedua server echo, permintaan ke salah satu server echo dianggap sebagai permintaan lintas origin. Namun, server echo pertama yang berjalan di localhost:5000/ dikonfigurasi untuk mendukung CORS (Anda dapat membuka echo-servers/cors-server.js dan memeriksa konfigurasi). Server echo baru, yang berjalan di localhost:5001/, tidak (itulah sebabnya kita mendapatkan error).

Penggunaan mode: no-cors memungkinkan pengambilan respons buram. Hal ini memungkinkan penggunaan untuk mendapatkan respons, tetapi mencegah akses ke respons dengan JavaScript (itulah sebabnya kita tidak dapat menggunakan validateResponse, readResponseAsText, atau showResponse). Respons masih dapat digunakan oleh API lain atau di-cache oleh service worker.

Mengubah header permintaan

Fetch juga mendukung pengubahan header permintaan. Hentikan server echo localhost:5001 (tanpa CORS) dan mulai ulang server echo localhost:5000 (CORS) dari bagian 6:

node echo-servers/cors-server.js

Pulihkan fungsi postRequest versi sebelumnya yang mengambil data dari localhost:5000/:

function postRequest() {
  const formData = new FormData(document.getElementById('msg-form'));
  fetch('http://localhost:5000/', {
    method: 'POST',
    body: formData
  })
    .then(validateResponse)
    .then(readResponseAsText)
    .then(showText)
    .catch(logError);
}

Sekarang gunakan Header interface untuk membuat objek Header di dalam fungsi postRequest yang disebut messageHeaders dengan header Content-Type sama dengan application/json.

Kemudian, tetapkan properti headers objek init menjadi variabel messageHeaders.

Perbarui properti body menjadi objek JSON yang di-stringifikasi, seperti:

JSON.stringify({ lab: 'fetch', status: 'fun' })

Setelah memperbarui kode, simpan file dan muat ulang halaman. Kemudian, klik POST Request.

Perhatikan bahwa permintaan yang di-echo sekarang memiliki Content-Type application/json (berbeda dengan multipart/form-data seperti sebelumnya).

Sekarang tambahkan header Content-Length kustom ke objek messageHeaders dan beri permintaan ukuran arbitrer.

Setelah Anda memperbarui kode, simpan file, muat ulang halaman, dan klik POST Request. Perhatikan bahwa header ini tidak diubah dalam permintaan yang di-echo.

Penjelasan

Header interface memungkinkan pembuatan dan modifikasi objek Header. Beberapa header, seperti Content-Type, dapat diubah dengan pengambilan. Yang lain, seperti Content-Length, dilindungi dan tidak dapat diubah (karena alasan keamanan).

Menetapkan header permintaan kustom

Fetch mendukung penetapan header kustom.

Hapus header Content-Length dari objek messageHeaders dalam fungsi postRequest. Tambahkan header kustom X-Custom dengan nilai arbitrer (misalnya 'X-CUSTOM': 'hello world').

Simpan skrip, muat ulang halaman, lalu klik POST Request.

Anda akan melihat bahwa permintaan yang di-echo memiliki properti X-Custom yang Anda tambahkan.

Sekarang tambahkan header Y-Custom ke objek Headers. Simpan skrip, muat ulang halaman, lalu klik POST Request.

Anda akan mendapatkan error yang mirip dengan ini di konsol:

Fetch API cannot load http://localhost:5000/. Request header field y-custom is not allowed by Access-Control-Allow-Headers in preflight response.

Penjelasan

Seperti permintaan lintas asal, header kustom harus didukung oleh server tempat resource diminta. Dalam contoh ini, server echo kami dikonfigurasi untuk menerima header X-Custom, tetapi tidak menerima header Y-Custom (Anda dapat membuka echo-servers/cors-server.js dan mencari Access-Control-Allow-Headers untuk melihatnya sendiri). Setiap kali header kustom ditetapkan, browser akan melakukan pemeriksaan preflight. Artinya, browser terlebih dahulu mengirimkan permintaan OPTIONS ke server, untuk menentukan metode dan header HTTP apa yang diizinkan oleh server. Jika server dikonfigurasi untuk menerima metode dan header permintaan asli, maka permintaan akan dikirim, jika tidak, error akan ditampilkan.

Untuk informasi selengkapnya

Kode solusi

Untuk mendapatkan salinan kode yang berfungsi, buka folder solution.

Sekarang Anda tahu cara menggunakan Fetch API.

Resource

Untuk melihat semua codelab dalam kursus pelatihan PWA, lihat codelab Selamat datang untuk kursus ini.