Индикаторы выполнения загрузки для выборки?

106

Я изо всех сил пытаюсь найти документацию или примеры реализации индикатора выполнения загрузки с помощью fetch .

Это единственная ссылка, которую я нашел до сих пор , в которой говорится:

События выполнения - это функция высокого уровня, которая пока не поступает в выборку. Вы можете создать свой собственный, просмотрев Content-Lengthзаголовок и используя сквозной поток для отслеживания полученных байтов.

Это означает, что вы можете явно обрабатывать ответы без какого-либо Content-Lengthдругого. И, конечно, даже если Content-Lengthэто так, это может быть ложь. С помощью потоков вы можете справиться с этой ложью, как хотите.

Как мне написать «сквозной поток для отслеживания отправленных байтов»? Если это имеет какое-то значение, я пытаюсь сделать это для загрузки изображений из браузера в Cloudinary .

ПРИМЕЧАНИЕ : меня не интересует библиотека Cloudinary JS , поскольку она зависит от jQuery, а мое приложение - нет. Меня интересует только потоковая обработка, необходимая для этого с помощью собственного javascript и fetchполифилла Github .


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

Neezer
источник

Ответы:

46

Потоки начинают появляться на веб-платформе ( https://jakearchibald.com/2016/streams-ftw/ ), но это еще только начало.

Вскоре вы сможете предоставить поток в качестве тела запроса, но открытый вопрос заключается в том, связано ли потребление этого потока с загруженными байтами.

Конкретные перенаправления могут привести к повторной передаче данных в новое место, но потоки не могут «перезапускаться». Мы можем исправить это, превратив тело в обратный вызов, который можно вызывать несколько раз, но мы должны быть уверены, что раскрытие количества перенаправлений не является утечкой безопасности, поскольку это будет первый раз, когда на платформе JS может обнаружить это.

Некоторые задаются вопросом, есть ли вообще смысл связывать потребление потока с загруженными байтами.

Короче говоря: это пока невозможно, но в будущем это будет обрабатываться либо потоками, либо переданным обратным вызовом более высокого уровня fetch().

Яффо
источник
7
Очень жаль. Принимаю это сейчас, но когда это станет реальностью, я надеюсь, что кто-то еще опубликует обновленное решение! :)
neezer 02
1
Обновление - отображение прогресса с API извлечения с использованием потоков - twitter.com/umaar/status/917789464658890753/photo/1
Эйтан Пир
2
@EitanPeer Отлично. Будет ли работать подобное при загрузке, например POST?
Майкл
4
@EitanPeer Но вопрос в прогрессе загрузки, а не в загрузке
Джон Балвин Ариас
1
Сейчас 2020 год, почему до сих пор нет возможности сделать это :(
MHA15,
25

Мое решение - использовать axios , которая хорошо это поддерживает:

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

У меня есть пример использования этого в реакции на github.

Двжонстон
источник
2
Это тоже было моим решением. Axios, кажется, очень хорошо вписывается в эту форму.
Джейсон Райс
1
Есть ли axiosпольза fetchили XMLHttpRequestпод капотом?
Дай
3
XMLHttpRequest. Если вы используете это для реакции native, имейте в виду, что XMLHttpRequest кажется ОЧЕНЬ ОЧЕНЬ медленным для анализа больших ответов json по сравнению с fetch (примерно в 10 раз медленнее, и он замораживает весь поток пользовательского интерфейса).
Криштиану Коэльо
29
Не отвечает на вопрос! Если вопрос в том, "как сделать x по y?" сказать «сделай x вместо z» - неприемлемый ответ.
Дерек Хендерсон
4
Это не отвечает на вопрос, особенно потому, axiosчто не используется fetchпод капотом и не имеет такой поддержки. Буквально сейчас для них пишу .
sgammon
7

Я не думаю, что это возможно. В проекте говорится:

в настоящее время он отсутствует [ по сравнению с XHR ], когда дело доходит до выполнения запроса


(старый ответ):
Первый пример в главе о Fetch API дает некоторое представление о том, как:

Если вы хотите получать данные тела постепенно:

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

Помимо использования Promiseконструктора antipattern , вы можете видеть, что response.bodyэто Stream, из которого вы можете читать побайтово с помощью Reader, и вы можете запускать событие или делать что угодно (например, регистрировать прогресс) для каждого из них.

Однако спецификация Streams , похоже, не совсем закончена, и я понятия не имею, работает ли это уже в какой-либо реализации выборки.

Берги
источник
14
Однако, если я правильно прочитал этот пример, это будет для загрузки файла через fetch. Меня интересуют индикаторы прогресса загрузки файла.
neezer
Ой, в этой цитате говорится о получении байтов, что меня смутило.
Bergi
1
@Bergi Обратите внимание, Promiseконструктор не нужен. Response.body.getReader()возвращает Promise. См. Раздел Как устранить ошибку Uncaught RangeError при загрузке json большого размера
guest271314
3
@ guest271314 да, я уже исправил это в источнике цитаты. И нет, getReaderне возвращает обещание. Понятия не имею, какое это имеет отношение к сообщению, на которое вы ссылаетесь.
Берги
@Bergi Да, вы правы .getReader(), .read()метод возвращает Promise. Это то, что пытался передать. Ссылка намекает на предпосылку, что если можно проверить прогресс для загрузки, то можно проверить и для загрузки. Составьте шаблон, который в значительной степени возвращает ожидаемый результат; это прогресс для fetch()загрузки. Не нашли способ или объект в jsfiddle, вероятно , отсутствует что - то простое. Очень быстрое тестирование при загрузке файла без имитации условий сети; хотя только что вспомнил . echoBlobFilelocalhostNetwork throttling
guest271314
7

Обновление: как говорится в принятом ответе, сейчас это невозможно. но приведенный ниже код некоторое время решал нашу проблему. Должен добавить, что, по крайней мере, нам пришлось перейти на использование библиотеки, основанной на 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');
}

благодаря этой ссылке: https://jakearchibald.com/2016/streams-ftw/

Hosseinmp76
источник
3
Хорошо, но применимо ли это и к загрузкам?
Ядро
@kernel Я попытался выяснить это, но не смог. и мне нравится находить способ сделать это и для загрузки.
Hosseinmp76 08
2
content-length! == длина тела. Когда используется сжатие http (обычно для больших загрузок), длина содержимого - это размер после сжатия http, а длина - это размер после извлечения файла.
Ferrybig
1
В вашем коде предполагается, что длина заголовка содержимого указывает количество байтов, которое будет загружено при выборке. Это не всегда так, поэтому ваш код не может показывать прогресс пользователю, так как bytesReceivedстановится больше, чемtotal
Ferrybig
1
Более того, даже браузер не знает заранее фактическую длину контента. Все, что вы получите, это индикатор прогресса после сжатия. Например, если вы загружаете zip-файл с неравномерно распределенной степенью сжатия (некоторые файлы случайные, некоторые с низкой энтропией), вы заметите, что индикатор выполнения сильно искажен.
elslooo 07
4

Поскольку ни один из ответов не решает проблему.

Просто ради реализации вы можете определить скорость загрузки с помощью некоторого небольшого начального фрагмента известного размера, а время загрузки можно рассчитать с помощью длины содержимого / скорости загрузки. Вы можете использовать это время как оценку.

Шишир Арора
источник
3
Очень умный, приятный трюк, который можно использовать, пока мы ждем решения в реальном времени :)
Magix
16
Слишком рискованно для меня. Не хотел бы, чтобы индикатор выполнения копирования файла в Windows заканчивался индикатором выполнения
Джек Гиффин,
2

Возможный обходной путь - использовать new Request()конструктор, а затем проверить Request.bodyUsed Booleanатрибут

Получатель bodyUsedатрибута должен возвращать истину, если disturbedи ложь в противном случае.

чтобы определить, является ли поток distributed

BodyГоворят, что объект, реализующий миксин, имеет значение disturbedif body, отличное от нуля, а это stream- disturbed.

Вернуть fetch() Promiseизнутри .then()цепочку к рекурсивному .read()вызову, ReadableStreamкогда Request.bodyUsedравно true.

Обратите внимание, что этот подход не считывает байты, Request.bodyпоскольку байты передаются в конечную точку. Кроме того, загрузка может завершиться задолго до того, как какой-либо ответ будет полностью возвращен браузеру.

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

}
гость271314
источник
-2
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}%`;
}
Леон Гилядов
источник
Я хочу поблагодарить Бенджамина Грюнбаума за весь ответ. Потому что я узнал об этом из его лекции.
Леон Гилядов
@LeonGilyadov Где-нибудь доступна онлайн-лекция? Ссылка на первоисточник было бы неплохо.
Марк Эмери
@MarkAmery Вот оно: youtube.com/watch?v=Ja8GKkxahCo (лекция
читалась
13
Вопрос в загрузке, а не в загрузке.
sarneeh
проблема с прогрессом загрузки - это когда вы хотите загрузить (с загрузкой проблем нет)
Камил Келчевски,
-6

Ключевой частью является ReadableStreamobj_response .body≫.

Образец:

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

Вы можете протестировать его запуск на огромной странице, например, https://html.spec.whatwg.org/ и https://html.spec.whatwg.org/print.pdf . CtrlShiftJ и загрузите код в.

(Проверено в Chrome.)

Pacerier
источник
1
Этот ответ получает минус баллы, но никто не объясняет, почему ставят минус балл - поэтому я даю +1
Камил Келчевски
5
Он получает от меня -1, потому что он не имеет отношения к загрузке .
Брэд
Я считаю, что он получил -1, потому что, похоже, он пишет миниатюрную версию javascript
TomasJ