Carregar indicadores de progresso para buscar?


99

Estou lutando para encontrar documentação ou exemplos de implementação de um indicador de progresso de upload usando fetch .

Esta é a única referência que encontrei até agora , que afirma:

Os eventos de progresso são um recurso de alto nível que não chegará em busca por enquanto. Você pode criar o seu próprio olhando para o Content-Lengthcabeçalho e usando um fluxo de passagem para monitorar os bytes recebidos.

Isso significa que você pode lidar explicitamente com as respostas, sem ser Content-Lengthdiferente. E, claro, mesmo se Content-Lengthestiver lá, pode ser mentira. Com os streams, você pode lidar com essas mentiras da maneira que quiser.

Como eu escreveria "um fluxo de passagem para monitorar os bytes" enviados? Se fizer alguma diferença, estou tentando fazer isso para fazer uploads de imagens do navegador para Cloudinary .

NOTA : Eu estou não interessados na biblioteca Cloudinary JS , uma vez que depende jQuery e meu aplicativo não. Estou interessado apenas no processamento de stream necessário para fazer isso com javascript nativo e fetchpolyfill do Github .


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


4
@Magix Consulte Abortando uma busca: The Next Generation # 447
guest271314

Respostas:


44

Os fluxos estão começando a chegar à plataforma da web ( https://jakearchibald.com/2016/streams-ftw/ ), mas ainda é cedo.

Em breve, você poderá fornecer um fluxo como o corpo de uma solicitação, mas a questão em aberto é se o consumo desse fluxo está relacionado aos bytes carregados.

Redirecionamentos específicos podem resultar na retransmissão de dados para o novo local, mas os fluxos não podem "reiniciar". Podemos corrigir isso transformando o corpo em um retorno de chamada que pode ser chamado várias vezes, mas precisamos ter certeza de que expor o número de redirecionamentos não é um vazamento de segurança, já que seria a primeira vez na plataforma JS poderia detectar isso.

Alguns estão questionando se faz sentido vincular o consumo de stream aos bytes carregados.

Resumindo: isso ainda não é possível, mas no futuro isso será tratado por streams ou algum tipo de retorno de chamada de nível superior transmitido fetch().


7
Que pena. Aceito por enquanto, mas quando isso se tornar realidade, espero que alguém poste uma solução atualizada! :)
neezer

1
Atualização - mostrando o progresso com fetch API usando streams - twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer

2
@EitanPeer Nice. Uma coisa semelhante funcionará para fazer upload, por exemplo, POST?
Michael de

4
@EitanPeer Mas, a questão é sobre o progresso no upload, não no download
John Balvin Arias,

1
agora é 2020, por que ainda não há como fazer isso :(
MHA15

23

Minha solução é usar axios , que suporta isso muito bem:

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

Eu tenho um exemplo para usar isso no react no github.


2
Essa foi a minha solução também. Axios parece se encaixar muito bem no molde.
Jason Rice

1
Faz axiosuso fetchou XMLHttpRequestsob o capô?
Dai

3
XMLHttpRequest. Se você estiver usando isso para reagir nativo, observe que XMLHttpRequest parece MUITO, MUITO lento para analisar grandes respostas json em comparação com a busca (cerca de 10 vezes mais lento e congela todo o thread de interface do usuário).
Cristiano Coelho

21
Não responde a pergunta! Se a pergunta for "como você faz x em y?" dizer "faça x em z em vez disso" não é uma resposta aceitável.
Derek Henderson

3
Isso não responde à pergunta, especialmente porque axiosnão usa fetchsob o capô e não tem tal suporte. Estou literalmente escrevendo agora para eles.
sgammon

7

Não acho que seja possível. O rascunho declara:

atualmente falta [ em comparação com XHR ] quando se trata de solicitar progressão


(resposta antiga):
O primeiro exemplo no capítulo Fetch API dá algumas dicas sobre como:

Se você deseja receber os dados corporais progressivamente:

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

Além do uso do Promiseantipadrão de construtor , você pode ver que response.bodyé um Stream do qual você pode ler byte por byte usando um Reader e pode disparar um evento ou fazer o que quiser (por exemplo, registrar o progresso) para cada um deles.

No entanto, a especificação do Streams não parece estar totalmente concluída e não tenho ideia se isso já funciona em qualquer implementação de busca.


11
Se eu li esse exemplo corretamente, no entanto, isso seria para baixar um arquivo via fetch. Estou interessado em indicadores de progresso para enviar um arquivo.
neezer

Opa, essa citação fala sobre o recebimento de bytes, o que me confundiu.
Bergi 01 de

@Bergi Nota, o Promiseconstrutor não é necessário. Response.body.getReader()retorna a Promise. Consulte Como resolver o
erro de alcance não detectado

3
@ guest271314 sim, corrigi na fonte da citação. E não, getReadernão retorna uma promessa. Não tenho ideia do que isso tem a ver com a postagem que você vinculou.
Bergi

@Bergi Sim, você está correto .getReader(). O .read()método retorna a Promise. Isso é o que estava tentando transmitir. O link é uma alusão à premissa de que se o progresso pode ser verificado para download, o progresso pode ser verificado para upload. Monte um padrão que retorne o resultado esperado, em um grau apreciável; isso é progresso para fetch()upload. Não encontrei um caminho para echoum objeto Blobou Fileem jsfiddle, provavelmente faltando algo simples. Testar localhostarquivos de uploads muito rapidamente, sem imitar as condições da rede; embora apenas lembrado Network throttling.
guest271314

6

Atualização: como diz a resposta aceita, é impossível agora. mas o código abaixo tratou do nosso problema por algum tempo. Devo acrescentar que pelo menos tivemos que passar a usar uma biblioteca baseada em 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');
}

graças a este link: https://jakearchibald.com/2016/streams-ftw/


2
Legal, mas isso se aplica a uploads também?
kernel

@kernel tentei descobrir mas não consegui. e eu gosto de encontrar uma maneira de fazer isso para upload também.
Hosseinmp76

O mesmo, mas até agora não tive muita sorte em encontrar / criar um exemplo de upload funcional.
kernel de

1
content-length! == comprimento do corpo. Quando a compactação http é usada (comum para downloads grandes), o comprimento do conteúdo é o tamanho após a compactação http, enquanto o comprimento é o tamanho após a extração do arquivo.
Ferrybig de

@Ferrybig Não entendi. usei a igualdade em algum lugar?
Hosseinmp76

4

Uma vez que nenhuma das respostas resolve o problema.

Apenas para fins de implementação, você pode detectar a velocidade de upload com um pequeno pedaço inicial de tamanho conhecido e o tempo de upload pode ser calculado com content-length / upload-speed. Você pode usar este tempo como estimativa.


3
Muito inteligente, bom truque para usar enquanto esperamos por uma solução em tempo real :)
Magix


2

Uma possível solução alternativa seria utilizar o new Request()construtor e verificar o Request.bodyUsed Booleanatributo

O bodyUsedgetter do atributo deve retornar verdadeiro se disturbed, e falso caso contrário.

para determinar se o fluxo é distributed

BodyDiz-se disturbedque um objeto que implementa o mixin bodyé não nulo e streamé disturbed.

Retorne a chamada recursiva fetch() Promisede dentro .then()encadeada para quando for igual a ..read()ReadableStreamRequest.bodyUsedtrue

Observe que a abordagem não lê os bytes do Request.bodyconforme os bytes são transmitidos para o terminal. Além disso, o upload pode ser concluído muito antes de qualquer resposta ser retornada por completo ao navegador.

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

Quero agradecer a Benjamin Gruenbaum por toda a resposta. Porque aprendi com sua palestra.
Leon Gilyadov

@LeonGilyadov A palestra está disponível online em algum lugar? Um link para a fonte seria bom.
Mark Amery

@MarkAmery Aqui está: youtube.com/watch?v=Ja8GKkxahCo (a palestra foi dada em hebraico)
Leon Gilyadov

11
A questão é sobre upload, não download.
sarneeh

o problema com o progresso da busca é quando você deseja fazer o upload (não há problema com o download)
Kamil Kiełczewski

-4

A parte principal é ReadableStreamobj_response .body≫.

Amostra:

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

Você pode testar sua execução em uma página enorme, por exemplo, https://html.spec.whatwg.org/ e https://html.spec.whatwg.org/print.pdf . CtrlShiftJ e carregue o código em.

(Testado no Chrome.)


Esta resposta recebe pontos negativos, mas ninguém explica por que dar pontos negativos - então eu dou +1
Kamil Kiełczewski

3
Recebe -1 de mim porque não é relevante para o upload .
Brad
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.