1 September 2021

Fetch: Kemajuan Download

Metode fetch memungkinkan untuk melacak kemajuan proses download.

Harap diperhatikan: Saat ini belum ada cara bagi metode fetch untuk melacak kemajuan proses upload. Untuk tujuan tersebut, gunakan XMLHttpRequest, kita akan membahasnya nanti.

Untuk melacak kemajuan download, kita dapat menggunakan properti response.body. Itu adalah ReadableStream ??? sebuah objek khusus yang menyediakan potongan demi potongan response.body saat didapatkan. Readable streams dideskripsikan pada spesifikasi Streams API.

Tidak seperti response.text(), response.json() dan metode lainnya, response.body memberikan kontrol penuh atas proses pembacaan dan kita dapat menghitung berapa banyak data yang diterima setiap saat.

Berikut adalah sketsa kode yang membaca respon dari response.body:

// sebagai ganti response.json() dan metode lainnya
const reader = response.body.getReader();

// perulangan tidak terbatas ketika proses mengunduh
while (true) {
  // done bernilai benar (true) untuk bagian terakhir
  // value adalah Uint8Array dari bagian bytes
  const { done, value } = await reader.read();

  if (done) {
    break;
  }

  console.log(`Diterima ${value.length} bytes`);
}

Hasil dari pemanggilan await reader.read() adalah objek dengan dua properti:

  • done ??? true saat proses pembacaan selesai, jika tidak false.
  • value ??? larik dengan tipe bytes: Uint8Array.
Tolong dicatat:

Streams API juga mendeskripsikan iterasi asynchronous melalui ReadableStream dengan perulangan for await..of, tetapi itu tidak sepenuhnya didukung (lihat ), sehingga kita menggunakan perulangan while.

Kita menerima potongan respon pada perulangan sampai proses loading selesai, yaitu: sampai done bernilai true.

Untuk mencatat kemajuan, kita hanya perlu setiap value dari fragment yang diterima untuk ditambahkan nilai panjangnya ke penghitung.

Berikut adalah contoh penuh yang menerima respon dan mencatat kemajuan pada console, Langkah-langkah yang dapat diikuti:

// Langkah 1: mulai fetch dan dapatkan reader
let response = await fetch(
  'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100'
);

const reader = response.body.getReader();

// Langkah 2: dapatkan panjang total (data)
const contentLength = +response.headers.get('Content-Length');

// Langkah 3: Membaca data
let receivedLength = 0; // Menerima banyak bytes saat ini
let chunks = []; // larik potongan biner yang diterima (Meliputi body respon)
while (true) {
  const { done, value } = await reader.read();

  if (done) {
    break;
  }

  chunks.push(value);
  receivedLength += value.length;

  console.log(`Diterima ${receivedLength} dari ${contentLength}`);
}

// Langkah 4: Menyatukan potongan-potongan menjadi satu Uint8Array
let chunksAll = new Uint8Array(receivedLength); // (4.1)
let position = 0;
for (let chunk of chunks) {
  chunksAll.set(chunk, position); // (4.2)
  position += chunk.length;
}

// Langkah 5: Mengubah menjadi string
let result = new TextDecoder('utf-8').decode(chunksAll);

// Selesai
let commits = JSON.parse(result);
alert(commits[0].author.login);

Mari kita jelaskan langkah demi langkah:

  1. Kita menggunakan fetch seperti biasa, tetapi sebagai ganti penggunaan response.json(), kita menggunakan stream reader response.body.getReader(). Perlu dicatat, kita tidak dapat menggunakan kedua metode ini untuk membaca respon yang sama: baik menggunakan reader atau metode respon untuk mendapatkan hasilnya.

  2. Sebelum proses pembacaan, kita bisa mendapatkan keseluruhan panjang respon dari Content-Length header.

    Itu kemungkinan tidak ada untuk permintaan lintas sumber (lihat bagian Fetch: *request Cross-Origin*) dan secara teknis server tidak harus menyetelnya. Tetapi biasanya ada.

  3. Penggil await reader.read() sampai selesai.

    Kita mengumpulkan potongan respon di larik chunks. Itu penting karena setelah respon diterima, kita tidak akan dapat ???membaca ulang??? menggunakan response.json() atau dengan cara lain (Anda dapat mencobanya dan akan ada galat)

  4. Pada akhirnya, kita memiliki chunks ??? sebuah larik dari potongan byte ??Uint8Array`. Kita perlu menggabungkannya menjadi satu data tunggal. Sayangnya, tidak ada metode tunggal yang dapat menggabungkannya, jadi ada beberapa kode untuk melakukannya:

    1. Kita membuat chunksAll = new Uint8Array (receivedLength) ??? larik dengan tipe yang sama dengan panjang gabungan.
    2. Kemudian gunakan metode .set (chunk, position) untuk menyalin setiap chunk satu demi satu di dalamnya.
  5. Kita mendapatkan hasilnya di chunksAll. Ini adalah larik byte, bukan string.

    Untuk membuat string, kita perlu menafsirkan byte ini. TextDecoder bawaan dapat melakukan hal itu. Lalu kita bisa JSON.parse, jika diperlukan.

    Bagaimana jika kita membutuhkan konten biner, bukan string? Itu bahkan lebih sederhana. Ganti langkah 4 dan 5 dengan satu baris yang membuat Blob dari semua potongan:

    let blob = new Blob(chunks);

Pada akhirnya kita memiliki hasil (sebagai string atau blob, apa pun yang Anda inginkan), dan pelacakan kemajuan dalam prosesnya.

Sekali lagi, harap diperhatikan, itu bukan untuk kemajuan upload (sekarang belum ada cara dengan fetch), hanya untuk kemajuan download.

Dan juga, jika ukurannya tidak diketahui, kita harus memeriksa acceptLength di loop dan menghentikannya setelah mencapai batas tertentu. Sehingga chunks tidak akan melebihkan memori.

Peta tutorial