¿Subir indicadores de progreso para recuperar?

103

Estoy luchando por encontrar documentación o ejemplos de implementación de un indicador de progreso de carga usando fetch .

Esta es la única referencia que he encontrado hasta ahora , que dice:

Los eventos de progreso son una característica de alto nivel que no llegará a buscar por ahora. Puede crear el suyo propio mirando el Content-Lengthencabezado y utilizando un flujo de transferencia para monitorear los bytes recibidos.

Esto significa que puede manejar explícitamente las respuestas sin una forma Content-Lengthdiferente. Y, por supuesto, incluso si Content-Lengthestá ahí, puede ser una mentira. Con las corrientes puedes manejar estas mentiras como quieras.

¿Cómo escribiría "un flujo de paso a través para monitorear los bytes" enviados? Si hace algún tipo de diferencia, estoy tratando de hacer esto para impulsar la carga de imágenes desde el navegador a Cloudinary .

NOTA : estoy no interesado en la biblioteca Cloudinary JS , ya que depende de jQuery y mi aplicación no lo hace. Solo estoy interesado en el procesamiento de transmisión necesario para hacer esto con javascript nativo y fetchpolyfill de Github .


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

neezer
fuente
4
@Magix See Aborting a fetch: The Next Generation # 447
guest271314

Respuestas:

44

Las transmisiones están comenzando a aterrizar en la plataforma web ( https://jakearchibald.com/2016/streams-ftw/ ) pero aún son los primeros días.

Pronto podrá proporcionar un flujo como el cuerpo de una solicitud, pero la pregunta abierta es si el consumo de ese flujo se relaciona con los bytes cargados.

Los redireccionamientos particulares pueden resultar en la retransmisión de datos a la nueva ubicación, pero los flujos no pueden "reiniciarse". Podemos solucionar esto convirtiendo el cuerpo en una devolución de llamada que se puede llamar varias veces, pero debemos estar seguros de que exponer la cantidad de redireccionamientos no es una fuga de seguridad, ya que sería la primera vez en la plataforma que JS podría detectar eso.

Algunos se preguntan si tiene sentido vincular el consumo de flujo con los bytes cargados.

En pocas palabras: esto aún no es posible, pero en el futuro se manejará mediante transmisiones o mediante algún tipo de devolución de llamada de nivel superior fetch().

JaffaTheCake
fuente
7
Demasiado. Aceptando esto por ahora, pero cuando esto se convierta en realidad, ¡espero que alguien más publique una solución actualizada! :)
neezer
1
Actualización: muestra el progreso con la API de recuperación mediante transmisiones: twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer
2
@EitanPeer Nice. ¿Funcionará algo similar para la carga, por ejemplo, POST?
Michael
4
@EitanPeer Pero, la pregunta es sobre el progreso en la carga, no en la descarga
John Balvin Arias
1
es 2020 ahora, por qué todavía no hay forma de hacer esto :(
MHA15
23

Mi solución es usar axios , que admite esto bastante bien:

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

Tengo un ejemplo para usar esto en reaccionar en github.

Dwjohnston
fuente
2
Esa también fue mi solución. Axios parece encajar muy bien en el molde.
Jason Rice
1
¿ axiosUso fetcho XMLHttpRequestbajo el capó?
Dai
3
XMLHttpRequest. Si está usando esto para reaccionar nativo, tenga en cuenta que XMLHttpRequest parece ser MUY MUY lento para analizar respuestas json grandes en comparación con fetch (aproximadamente 10 veces más lento y congela todo el hilo de la interfaz de usuario).
Cristiano Coelho
23
¡No responde la pregunta! Si la pregunta es "¿cómo se hace x en y?" decir "haz x en z en su lugar" no es una respuesta aceptable.
Derek Henderson
4
Esto no responde a la pregunta, especialmente porque axiosno se usa fetchdebajo del capó y no tiene tal soporte. Literalmente lo estoy escribiendo ahora para ellos.
sgammon
7

No creo que sea posible. El borrador dice:

actualmente falta [ en comparación con XHR ] cuando se trata de solicitar progresión


(respuesta anterior):
el primer ejemplo en el capítulo Fetch API brinda información sobre cómo:

Si desea recibir los datos corporales de forma progresiva:

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

Aparte de su uso del Promiseconstructor antipattern , puede ver queresponse.body es un Stream desde el que puede leer byte a byte usando un Reader, y puede disparar un evento o hacer lo que quiera (por ejemplo, registrar el progreso) para cada uno de ellos.

Sin embargo, la especificación de Streams no parece estar del todo terminada, y no tengo idea de si esto ya funciona en alguna implementación de recuperación.

Bergi
fuente
11
Sin embargo, si leo ese ejemplo correctamente, esto sería para descargar un archivo a través de fetch. Me interesan los indicadores de progreso para cargar un archivo.
neezer
Vaya, esa cita habla de recibir bytes, lo que me confundió.
Bergi
@Bergi Nota, el Promiseconstructor no es necesario. Response.body.getReader()devuelve un Promise. Consulte Cómo resolver un
error de rango no detectado
3
@ guest271314 sí, ya lo he arreglado en la fuente de la cita. Y no, getReaderno devuelve una promesa. No tengo idea de qué tiene esto que ver con la publicación que vinculó.
Bergi
@Bergi Sí, .getReader()el .read()método correcto devuelve a Promise. Eso es lo que intentaba transmitir. El enlace es para aludir a la premisa de que si se puede verificar el progreso para la descarga, se puede verificar el progreso para la carga. Elabore un patrón que arroje el resultado esperado, en un grado apreciable; ese es el progreso para la fetch()carga. No he encontrado la forma de echoun objeto Blobu Fileen jsfiddle, probablemente se esté perdiendo algo simple. Probar el localhostarchivo de carga muy rápidamente, sin imitar las condiciones de la red; aunque acabo de recordar Network throttling.
guest271314
6

Actualización: como dice la respuesta aceptada, ahora es imposible. pero el siguiente código solucionó nuestro problema durante algún tiempo. Debo agregar que al menos tuvimos que cambiar al uso de una biblioteca basada en 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');
}

gracias a este enlace: https://jakearchibald.com/2016/streams-ftw/

Hosseinmp76
fuente
2
Bien, pero ¿se aplica también a las cargas?
kernel
@kernel Traté de averiguarlo pero no pude hacerlo. y también me gusta encontrar una manera de hacer esto para subir.
Hosseinmp76
Lo mismo, pero hasta ahora no tuve la suerte de encontrar / crear un ejemplo de carga funcional.
kernel
1
content-length! == longitud del cuerpo. Cuando se usa la compresión http (común para descargas grandes), la longitud del contenido es el tamaño después de la compresión http, mientras que la longitud es el tamaño después de que se extrajo el archivo.
Ferrybig
@Ferrybig No entendí tu punto. ¿Usé la igualdad en alguna parte?
Hosseinmp76
4

Dado que ninguna de las respuestas resuelve el problema.

Solo por el bien de la implementación, puede detectar la velocidad de carga con una pequeña porción inicial de tamaño conocido y el tiempo de carga se puede calcular con la longitud del contenido / la velocidad de carga. Puede utilizar este tiempo como estimación.

Shishir Arora
fuente
3
Un truco muy inteligente y agradable para usar mientras esperamos una solución en tiempo real :)
Magix
14
Demasiado arriesgado para mí. No quisiera terminar como la barra de progreso del archivo de copia de Windows
Jack Giffin
2

Una posible solución sería utilizar el new Request()constructor y luego verificar el Request.bodyUsed Booleanatributo

El bodyUsedcaptador del atributo debe devolver verdadero si disturbedy falso en caso contrario.

para determinar si la transmisión es distributed

Se Bodydice que un objeto que implementa el mixin es disturbedif bodyno es nulo y lo streames disturbed.

Devuelve el fetch() Promisedesde dentro .then()encadenado a la .read()llamada recursiva de ReadableStreamcuando Request.bodyUsedes igual a true.

Tenga en cuenta que el enfoque no lee los bytes de Request.bodylos bytes a medida que se transmiten al punto final. Además, la carga podría completarse mucho antes de que se devuelva la respuesta completa al 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));

}
invitado271314
fuente
-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}%`;
}
Leon Gilyadov
fuente
Quiero darle crédito a Benjamin Gruenbaum por toda la respuesta. Porque lo aprendí de su conferencia.
Leon Gilyadov
@LeonGilyadov ¿Está la conferencia disponible en línea en cualquier lugar? Un enlace a la fuente estaría bien.
Mark Amery
@MarkAmery Aquí está: youtube.com/watch?v=Ja8GKkxahCo (la conferencia se dio en hebreo)
Leon Gilyadov
11
La pregunta es sobre cargar, no descargar.
sarneeh
el problema con el progreso de la recuperación es cuando desea cargar (no hay problema con la descarga)
Kamil Kiełczewski
-5

La parte clave es ReadableStreamobj_response .body≫.

Muestra:

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

Puede probar su ejecución en una página enorme, por ejemplo, https://html.spec.whatwg.org/ y https://html.spec.whatwg.org/print.pdf . CtrlShiftJ y cargue el código en formato.

(Probado en Chrome).

Pacerier
fuente
1
Esta respuesta obtiene puntos negativos, pero nadie explica por qué dar un punto negativo, así que doy +1
Kamil Kiełczewski
4
Obtiene un -1 de mi parte porque no es relevante para la carga .
Brad