Unggah indikator kemajuan untuk diambil?


102

Saya kesulitan menemukan dokumentasi atau contoh penerapan indikator kemajuan upload menggunakan fetch .

Ini adalah satu-satunya referensi yang saya temukan sejauh ini , yang menyatakan:

Peristiwa kemajuan adalah fitur tingkat tinggi yang tidak akan datang untuk saat ini. Anda dapat membuatnya sendiri dengan melihat Content-Lengthheader dan menggunakan aliran pass-through untuk memantau byte yang diterima.

Ini berarti Anda dapat menangani respons secara eksplisit tanpa Content-Lengthperbedaan. Dan tentu saja, bahkan jika Content-Lengthada itu bisa menjadi kebohongan. Dengan aliran Anda dapat menangani kebohongan ini sesuka Anda.

Bagaimana saya menulis "aliran pass-through untuk memantau byte" yang dikirim? Jika ada perbedaan, saya mencoba melakukan ini untuk mendukung unggahan gambar dari browser ke Cloudinary .

CATATAN : Saya tidak tertarik dengan pustaka Cloudinary JS , karena bergantung pada jQuery dan aplikasi saya tidak. Saya hanya tertarik pada pemrosesan streaming yang diperlukan untuk melakukan ini dengan javascript asli dan fetchpolyfill Github .


https://fetch.spec.whatwg.org/#fetch-api


Jawaban:


44

Aliran mulai mendarat di platform web ( https://jakearchibald.com/2016/streams-ftw/ ) tetapi ini masih awal.

Anda akan segera dapat menyediakan aliran sebagai isi permintaan, tetapi pertanyaan terbukanya adalah apakah konsumsi aliran itu terkait dengan byte yang diunggah.

Pengalihan tertentu dapat mengakibatkan data dikirim ulang ke lokasi baru, tetapi aliran tidak dapat "dimulai ulang". Kita dapat memperbaikinya dengan mengubah body menjadi callback yang dapat dipanggil berkali-kali, tetapi kita perlu memastikan bahwa mengungkapkan jumlah pengalihan bukanlah kebocoran keamanan, karena ini akan menjadi pertama kalinya di platform JS bisa deteksi itu.

Beberapa mempertanyakan apakah masuk akal untuk menautkan konsumsi aliran ke byte yang diunggah.

Singkat cerita: ini belum memungkinkan, tetapi di masa mendatang ini akan ditangani baik oleh streaming, atau semacam panggilan balik tingkat tinggi yang diteruskan ke fetch().


7
Sangat buruk. Menerima ini untuk saat ini, tetapi ketika ini menjadi kenyataan, saya berharap orang lain akan memposting solusi yang diperbarui! :)
neezer

1
Pembaruan - menunjukkan kemajuan dengan fetch API menggunakan aliran - twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer

2
@Eitaneer Bagus. Akankah hal serupa bekerja untuk mengunggah, misalnya POST?
Michael

4
@EitanPeer Tapi, pertanyaannya adalah tentang kemajuan dalam pengunggahan, bukan pengunduhan
John Balvin Arias

1
Sekarang tahun 2020, mengapa masih belum ada cara untuk melakukan ini :(
MHA15

23

Solusi saya adalah menggunakan axios , yang mendukung ini dengan cukup baik:

      axios.request( {
        method: "post", 
        url: "/aaa", 
        data: myData, 
        onUploadProgress: (p) => {
          console.log(p); 
          //this.setState({
            //fileprogress: p.loaded / p.total
          //})
        }


      }).then (data => {
        //this.setState({
          //fileprogress: 1.0,
        //})
      })

Saya punya contoh untuk menggunakan ini dalam bereaksi di github.


2
Itu juga solusi saya. Axios tampaknya sangat cocok dengan cetakannya.
Jason Rice

1
Apakah axiosmenggunakan fetchatau XMLHttpRequestunder-the-hood?
Dai

3
XMLHttpRequest. Jika Anda menggunakan ini untuk react native, berhati-hatilah karena XMLHttpRequest tampaknya SANGAT SANGAT lambat untuk mengurai respons json yang besar jika dibandingkan dengan fetch (sekitar 10 kali lebih lambat, dan ini membekukan seluruh utas ui).
Cristiano Coelho

23
Tidak menjawab pertanyaan itu! Jika pertanyaannya adalah "bagaimana Anda melakukan x dalam y?" mengatakan "lakukan x di z sebagai gantinya" bukanlah jawaban yang dapat diterima.
Derek Henderson

4
Ini tidak menjawab pertanyaan, terutama karena axiostidak digunakan di fetchbawah tenda, dan tidak memiliki dukungan seperti itu. Saya benar-benar mengarangnya sekarang untuk mereka.
sgammon

7

Saya tidak berpikir itu mungkin. Draf tersebut menyatakan:

saat ini kurang [ dibandingkan dengan XHR ] dalam hal permintaan perkembangan


(jawaban lama):
Contoh pertama di bab Fetch API memberikan beberapa wawasan tentang cara:

Jika Anda ingin menerima data tubuh secara bertahap:

function consume(reader) {
  var total = 0
  return new Promise((resolve, reject) => {
    function pump() {
      reader.read().then(({done, value}) => {
        if (done) {
          resolve()
          return
        }
        total += value.byteLength
        log(`received ${value.byteLength} bytes (${total} bytes in total)`)
        pump()
      }).catch(reject)
    }
    pump()
  })
}

fetch("/music/pk/altes-kamuffel.flac")
  .then(res => consume(res.body.getReader()))
  .then(() => log("consumed the entire body without keeping the whole thing in memory!"))
  .catch(e => log("something went wrong: " + e))

Selain dari penggunaan Promiseantipattern konstruktor , Anda dapat melihatnyaresponse.body Stream dari mana Anda dapat membaca byte demi byte menggunakan Pembaca, dan Anda dapat mengaktifkan acara atau melakukan apa pun yang Anda suka (misalnya mencatat kemajuan) untuk masing-masing.

Namun, spesifikasi Streams tampaknya belum selesai, dan saya tidak tahu apakah ini sudah berfungsi dalam implementasi pengambilan apa pun.


11
Jika saya membaca contoh itu dengan benar, ini akan menjadi untuk mengunduh file melalui fetch. Saya tertarik dengan indikator kemajuan untuk mengupload file.
neezer

Ups, kutipan itu berbicara tentang menerima byte, yang membuat saya bingung.
Bergi

@Bergi Catatan, Promisekonstruktor tidak diperlukan. Response.body.getReader()mengembalikan a Promise. Lihat Cara mengatasi Kesalahan Rentang yang Tidak Tertangkap saat mengunduh json ukuran besar
guest271314

3
@ guest271314 ya, saya sudah memperbaikinya di sumber kutipan. Dan tidak, getReadertidak mengembalikan janji. Tidak tahu apa hubungannya ini dengan kiriman yang Anda tautkan.
Bergi

@Bergi Ya, Anda benar .getReader()'s .read()metode mengembalikan Promise. Demikian yang ingin disampaikan. Tautan ini untuk menyinggung premis bahwa jika kemajuan dapat diperiksa untuk pengunduhan, kemajuan dapat diperiksa untuk pengunggahan. Kumpulkan pola yang mengembalikan hasil yang diharapkan, pada tingkat yang cukup; itu kemajuan untuk fetch()mengunggah. Belum menemukan cara ke echosuatu Blobatau Fileobjek di jsfiddle, mungkin melewatkan sesuatu yang sederhana. Menguji localhostunggahan file dengan sangat cepat, tanpa meniru kondisi jaringan; meski baru ingat Network throttling.
guest271314

6

Pembaruan: karena jawaban yang diterima mengatakan tidak mungkin sekarang. tetapi kode di bawah ini menangani masalah kami untuk beberapa saat. Saya harus menambahkan bahwa setidaknya kita harus beralih menggunakan perpustakaan yang didasarkan pada XMLHttpRequest.

const response = await fetch(url);
const total = Number(response.headers.get('content-length'));

const reader = response.body.getReader();
let bytesReceived = 0;
while (true) {
    const result = await reader.read();
    if (result.done) {
        console.log('Fetch complete');
        break;
    }
    bytesReceived += result.value.length;
    console.log('Received', bytesReceived, 'bytes of data so far');
}

berkat tautan ini: https://jakearchibald.com/2016/streams-ftw/


2
Bagus, tetapi apakah itu juga berlaku untuk upload?
kernel

@kernel Saya mencoba mencari tahu tetapi saya tidak dapat melakukannya. dan saya juga suka mencari cara untuk melakukan ini untuk mengunggah.
Hosseinmp76

Sama sama, tetapi sejauh ini saya tidak terlalu beruntung menemukan / membuat contoh unggahan yang berfungsi.
kernel

1
content-length! == panjang badan. Ketika kompresi http digunakan (umum untuk unduhan besar), panjang konten adalah ukuran setelah kompresi http, sedangkan panjang adalah ukuran setelah file diekstraksi.
Ferrybig

@Ferrybig Saya tidak mengerti maksud Anda. apakah saya menggunakan persamaan di suatu tempat?
Hosseinmp76

4

Karena tidak ada jawaban yang memecahkan masalah.

Demi penerapan, Anda dapat mendeteksi kecepatan unggah dengan potongan awal kecil dari ukuran yang diketahui dan waktu unggah dapat dihitung dengan panjang konten / kecepatan unggah. Anda dapat menggunakan waktu ini sebagai perkiraan.


3
Trik yang sangat pintar dan bagus untuk digunakan sementara kita menunggu solusi waktu nyata :)
Magix

14
Terlalu beresiko bagiku. Tidak ingin berakhir seperti bilah kemajuan file salinan windows
Jack Giffin

2

Solusi yang mungkin adalah dengan menggunakan new Request()konstruktor lalu memeriksa Request.bodyUsed Booleanatribut

Pengambil bodyUsedatribut harus menampilkan true if disturbed, dan false jika sebaliknya.

untuk menentukan apakah aliran distributed

Objek yang mengimplementasikan Bodymixin dikatakan disturbedjika bodynon-null and its streamis disturbed.

Kembalikan fetch() Promisedari dalam .then()berantai ke .read()panggilan rekursif dari ReadableStreamsaat Request.bodyUsedsama dengantrue .

Catatan, pendekatan ini tidak membaca byte Request.bodysebagai byte dialirkan ke titik akhir. Selain itu, unggahan dapat diselesaikan dengan baik sebelum tanggapan apa pun dikembalikan sepenuhnya ke browser.

const [input, progress, label] = [
  document.querySelector("input")
  , document.querySelector("progress")
  , document.querySelector("label")
];

const url = "/path/to/server/";

input.onmousedown = () => {
  label.innerHTML = "";
  progress.value = "0"
};

input.onchange = (event) => {

  const file = event.target.files[0];
  const filename = file.name;
  progress.max = file.size;

  const request = new Request(url, {
    method: "POST",
    body: file,
    cache: "no-store"
  });

  const upload = settings => fetch(settings);

  const uploadProgress = new ReadableStream({
    start(controller) {
        console.log("starting upload, request.bodyUsed:", request.bodyUsed);
        controller.enqueue(request.bodyUsed);
    },
    pull(controller) {
      if (request.bodyUsed) {
        controller.close();
      }
      controller.enqueue(request.bodyUsed);
      console.log("pull, request.bodyUsed:", request.bodyUsed);
    },
    cancel(reason) {
      console.log(reason);
    }
  });

  const [fileUpload, reader] = [
    upload(request)
    .catch(e => {
      reader.cancel();
      throw e
    })
    , uploadProgress.getReader()
  ];

  const processUploadRequest = ({value, done}) => {
    if (value || done) {
      console.log("upload complete, request.bodyUsed:", request.bodyUsed);
      // set `progress.value` to `progress.max` here 
      // if not awaiting server response
      // progress.value = progress.max;
      return reader.closed.then(() => fileUpload);
    }
    console.log("upload progress:", value);
    progress.value = +progress.value + 1;
    return reader.read().then(result => processUploadRequest(result));
  };

  reader.read().then(({value, done}) => processUploadRequest({value,done}))
  .then(response => response.text())
  .then(text => {
    console.log("response:", text);
    progress.value = progress.max;
    input.value = "";
  })
  .catch(err => console.log("upload error:", err));

}

-3
const req = await fetch('./foo.json');
const total = Number(req.headers.get('content-length'));
let loaded = 0;
for await(const {length} of req.body.getReader()) {
  loaded = += length;
  const progress = ((loaded / total) * 100).toFixed(2); // toFixed(2) means two digits after floating point
  console.log(`${progress}%`); // or yourDiv.textContent = `${progress}%`;
}

Saya ingin memberikan penghargaan kepada Benjamin Gruenbaum untuk seluruh jawabannya. Karena saya mempelajarinya dari ceramahnya.
Leon Gilyadov

@LeonGilyadov Apakah kuliah tersedia online di mana saja? Tautan ke sumbernya akan bagus.
Mark Amery

@MarkAmery Ini dia: youtube.com/watch?v=Ja8GKkxahCo (kuliah diberikan dalam bahasa Ibrani)
Leon Gilyadov

11
Pertanyaannya adalah tentang mengunggah, bukan mengunduh.
sarneeh

masalah dengan kemajuan pengambilan adalah ketika Anda ingin mengunggah (tidak ada masalah dengan unduhan)
Kamil Kiełczewski

-5

Bagian kuncinya adalah ReadableStreamobj_response .body≫.

Sampel:

let parse=_/*result*/=>{
  console.log(_)
  //...
  return /*cont?*/_.value?true:false
}

fetch('').
then(_=>( a/*!*/=_.body.getReader(), b/*!*/=z=>a.read().then(parse).then(_=>(_?b:z=>z)()), b() ))

Anda dapat menguji menjalankannya di halaman besar misalnya https://html.spec.whatwg.org/ dan https://html.spec.whatwg.org/print.pdf . CtrlShiftJ dan muat kodenya.

(Diuji di Chrome.)


1
Jawaban ini mendapat poin minus tetapi tidak ada yang menjelaskan mengapa memberi poin minus - jadi saya memberi +1
Kamil Kiełczewski

4
Ini mendapat -1 dari saya karena tidak relevan dengan pengunggahan .
Brad
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.