Tải lên các chỉ báo tiến độ để tìm nạp?


99

Tôi đang loay hoay tìm tài liệu hoặc ví dụ về cách triển khai chỉ báo tiến trình tải lên bằng cách sử dụng tìm nạp .

Đây là tài liệu tham khảo duy nhất mà tôi tìm thấy cho đến nay , cho biết:

Sự kiện tiến độ là một tính năng cấp cao sẽ không được tải vào ngay bây giờ. Bạn có thể tạo của riêng mình bằng cách xem Content-Lengthtiêu đề và sử dụng luồng chuyển qua để theo dõi các byte nhận được.

Điều này có nghĩa là bạn có thể xử lý rõ ràng các câu trả lời mà không cần một cách Content-Lengthkhác. Và tất nhiên, ngay cả khi Content-Lengthcó nó vẫn có thể là một lời nói dối. Với các luồng, bạn có thể xử lý những lời nói dối này theo cách bạn muốn.

Tôi sẽ viết "một luồng chuyển qua để theo dõi các byte" được gửi như thế nào? Nếu nó tạo ra bất kỳ sự khác biệt nào, tôi đang cố gắng làm điều này để tăng cường tải hình ảnh từ trình duyệt lên Cloudinary .

LƯU Ý : Tôi không quan tâm đến thư viện Cloudinary JS , vì nó phụ thuộc vào jQuery và ứng dụng của tôi thì không. Tôi chỉ quan tâm đến việc xử lý luồng cần thiết để thực hiện điều này với javascript gốc và fetchpolyfill của Github .


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


Câu trả lời:


44

Các luồng đang bắt đầu cập bến nền tảng web ( https://jakearchibald.com/2016/streams-ftw/ ) nhưng vẫn còn là những ngày đầu.

Bạn sẽ sớm có thể cung cấp một luồng dưới dạng nội dung của một yêu cầu, nhưng câu hỏi mở là liệu việc tiêu thụ luồng đó có liên quan đến các byte được tải lên hay không.

Các chuyển hướng cụ thể có thể dẫn đến dữ liệu được truyền lại đến vị trí mới, nhưng các luồng không thể "khởi động lại". Chúng tôi có thể khắc phục điều này bằng cách chuyển phần thân thành một cuộc gọi lại có thể được gọi nhiều lần, nhưng chúng tôi cần đảm bảo rằng việc để lộ số lượng chuyển hướng không phải là một rò rỉ bảo mật, vì đây là lần đầu tiên trên nền tảng JS có thể phát hiện điều đó.

Một số người đang đặt câu hỏi liệu việc liên kết mức tiêu thụ luồng với byte đã tải lên có hợp lý hay không.

Câu chuyện ngắn: điều này chưa thể thực hiện được, nhưng trong tương lai, điều này sẽ được xử lý bởi các luồng hoặc một số loại gọi lại cấp cao hơn được chuyển vào fetch().


7
Quá tệ. Chấp nhận điều này ngay bây giờ, nhưng khi điều này trở thành hiện thực, tôi hy vọng rằng ai đó sẽ đăng một giải pháp cập nhật! :)
neezer

@ jaffa-the-cake có tin gì không?
mu3 16/12/16

1
Cập nhật - cho thấy sự tiến bộ với lấy API sử dụng suối - twitter.com/umaar/status/917789464658890753/photo/1
Peer Eitan

2
@EitanPeer Đẹp. Một thứ tương tự sẽ hoạt động để tải lên, ví dụ như ĐĂNG?
Michael

4
@EitanPeer Nhưng, câu hỏi là về tiến bộ trong việc tải lên, không phải trong tải
John Balvin Arias

23

Giải pháp của tôi là sử dụng axios , hỗ trợ khá tốt điều này:

      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,
        //})
      })

Tôi có ví dụ để sử dụng điều này trong phản ứng trên github.


2
Đó cũng là giải pháp của tôi. Axios có vẻ rất phù hợp với khuôn.
Jason Rice

1
axiossử dụng fetchhay XMLHttpRequestkhông?
Dai

3
XMLHttpRequest. Nếu bạn đang sử dụng điều này cho phản ứng gốc, hãy cẩn thận rằng XMLHttpRequest dường như RẤT RẤT RẤT chậm khi phân tích cú pháp các phản hồi json lớn khi so sánh với tìm nạp (chậm hơn khoảng 10 lần và nó đóng băng toàn bộ chuỗi ui).
Cristiano Coelho

21
Không trả lời câu hỏi! Nếu câu hỏi là "làm thế nào để bạn làm x trong y?" nói "do x in z thay thế" không phải là câu trả lời được chấp nhận.
Derek Henderson

3
Điều này không trả lời câu hỏi, đặc biệt là vì axioskhông sử dụng fetchdưới mui xe và không có hỗ trợ như vậy. Tôi thực sự là tác giả của nó bây giờ cho họ.
sgammon

7

Tôi không nghĩ nó có thể. Dự thảo nêu rõ:

nó hiện đang thiếu [ so với XHR ] khi yêu cầu tiến trình


(câu trả lời cũ):
Ví dụ đầu tiên trong chương Tìm nạp API cung cấp một số thông tin chi tiết về cách:

Nếu bạn muốn nhận dần dữ liệu cơ thể:

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))

Ngoài việc họ sử dụng phản vật lý Promisekhởi tạo , bạn có thể thấy đó response.bodylà một Luồng mà từ đó bạn có thể đọc từng byte bằng Trình đọc và bạn có thể kích hoạt một sự kiện hoặc làm bất cứ điều gì bạn thích (ví dụ: ghi lại tiến trình) cho mỗi người trong số họ.

Tuy nhiên, thông số kỹ thuật Luồng dường như chưa hoàn thiện và tôi không biết liệu điều này đã hoạt động trong bất kỳ triển khai tìm nạp nào chưa.


11
Tuy nhiên, nếu tôi đọc đúng ví dụ đó, thì đây sẽ là cách tải xuống tệp qua fetch. Tôi quan tâm đến các chỉ báo tiến độ để tải tệp lên .
neezer

Rất tiếc, trích dẫn đó nói về việc nhận byte, khiến tôi bối rối.
Bergi

@Bergi Lưu ý, hàm Promisetạo là không cần thiết. Response.body.getReader()trả về a Promise. Xem Làm thế nào để giải quyết vấn của router RangeError khi tải lớn kích thước json
guest271314

3
@ guest271314 ừ, mình đã sửa ở nguồn trích dẫn rồi. Và không, getReaderkhông trả lại một lời hứa. Không biết điều này có liên quan gì đến bài đăng bạn đã liên kết.
Bergi

@Bergi Vâng, bạn là chính xác .getReader()của .read()phương thức trả về một Promise. Đó là những gì đã cố gắng truyền đạt. Liên kết này ám chỉ tiền đề rằng nếu tiến trình có thể được kiểm tra để tải xuống, tiến trình có thể được kiểm tra để tải lên. Tập hợp một mô hình mang lại kết quả mong đợi, ở một mức độ đáng kể; đó là tiến trình fetch()tải lên. Chưa tìm thấy cách đến echomột Blobhoặc Fileđối tượng tại jsfiddle, có thể thiếu một cái gì đó đơn giản. Kiểm tra localhosttệp tải lên rất nhanh, không bắt chước các điều kiện mạng; dù chỉ mới nhớ Network throttling.
271314

6

Cập nhật: như câu trả lời được chấp nhận cho biết bây giờ là không thể. nhưng đoạn mã dưới đây đã xử lý vấn đề của chúng tôi đôi khi. Tôi nên nói thêm rằng ít nhất chúng ta đã phải chuyển sang sử dụng một thư viện dựa trên 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');
}

nhờ liên kết này: https://jakearchibald.com/2016/streams-ftw/


2
Đẹp, nhưng nó có áp dụng cho cả tải lên không?
kernel

@kernel Tôi đã cố gắng tìm hiểu nhưng tôi không thể làm được. và tôi cũng muốn tìm cách làm điều này để tải lên.
Hosseinmp76

Tương tự, nhưng cho đến nay tôi đã không quá may mắn khi tìm / tạo ra một ví dụ tải lên hoạt động.
kernel

1
content-length! == chiều dài của cơ thể. Khi nén http được sử dụng (phổ biến cho các tải xuống lớn), độ dài nội dung là kích thước sau khi nén http, trong khi độ dài là kích thước sau khi tệp đã được giải nén.
Ferrybig

@Ferrybig Tôi không hiểu ý bạn. Tôi đã sử dụng sự bình đẳng ở đâu đó?
Hosseinmp76

4

Vì không có câu trả lời nào giải quyết được vấn đề.

Chỉ vì mục đích triển khai, bạn có thể phát hiện tốc độ tải lên với một số đoạn nhỏ ban đầu có kích thước đã biết và thời gian tải lên có thể được tính theo thời lượng nội dung / tốc độ tải lên. Bạn có thể sử dụng thời gian này để ước tính.


3
Rất thông minh, lừa tốt đẹp để sử dụng trong khi chúng tôi chờ đợi một giải pháp thời gian thực :)
Magix


2

Một giải pháp khả thi là sử dụng hàm new Request()tạo sau đó kiểm tra Request.bodyUsed Booleanthuộc tính

Các bodyUsedgetter của thuộc tính phải trả về true nếu disturbed, và sai khác.

để xác định xem luồng có distributed

Một đối tượng thực hiện Bodymixin được cho là disturbedif bodykhông phải là null và nó streamdisturbed.

Trả về fetch() Promisetừ bên trong .then()chuỗi để .read()gọi đệ quy của một ReadableStreamkhi Request.bodyUsedbằng true.

Lưu ý, phương pháp này không đọc các byte Request.bodykhi các byte được truyền trực tiếp đến điểm cuối. Ngoài ra, quá trình tải lên có thể hoàn thành tốt trước khi bất kỳ phản hồi nào được trả lại đầy đủ cho trình duyệt.

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}%`;
}

Tôi muốn ghi công cho Benjamin Gruenbaum vì toàn bộ câu trả lời. Bởi vì tôi đã học được điều đó từ bài giảng của anh ấy.
Leon Gilyadov

@LeonGilyadov Bài giảng có trực tuyến ở bất cứ đâu không? Một liên kết đến nguồn sẽ tốt.
Mark Amery

@MarkAmery Đây rồi: youtube.com/watch?v=Ja8GKkxahCo (bài giảng được đưa ra bằng tiếng Do Thái)
Leon Gilyadov

11
Câu hỏi là về tải lên, không tải xuống.
sarneeh

vấn đề với tiến độ tìm nạp là khi bạn muốn tải lên (không có vấn đề gì với việc tải xuống)
Kamil Kiełczewski

-4

Phần quan trọng là ReadableStreamobj_response .body≫.

Mẫu vật:

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() ))

Bạn có thể chạy thử nó trên một trang lớn, ví dụ như https://html.spec.whatwg.org/https://html.spec.whatwg.org/print.pdf . CtrlShiftJ và tải mã vào.

(Đã thử nghiệm trên Chrome.)


Câu trả lời này bị trừ điểm nhưng không ai giải thích tại sao lại cho điểm trừ - vì vậy tôi cho +1
Kamil Kiełczewski

3
Tôi nhận được -1 vì nó không liên quan đến việc tải lên .
Brad
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.