Télécharger les indicateurs de progression pour la récupération?

101

J'ai du mal à trouver de la documentation ou des exemples de mise en œuvre d'un indicateur de progression de téléchargement à l'aide de fetch .

C'est la seule référence que j'ai trouvée jusqu'à présent , qui indique:

Les événements de progression sont une fonctionnalité de haut niveau qui n'arrivera pas dans la récupération pour le moment. Vous pouvez créer le vôtre en regardant l'en- Content-Lengthtête et en utilisant un flux d'intercommunication pour surveiller les octets reçus.

Cela signifie que vous pouvez gérer explicitement les réponses sans Content-Lengthautre. Et bien sûr, même si Content-Lengthc'est là, ça peut être un mensonge. Avec les flux, vous pouvez gérer ces mensonges comme vous le souhaitez.

Comment pourrais-je écrire "un flux de passage pour surveiller les octets" envoyés? Si cela fait une différence, j'essaie de le faire pour alimenter les téléchargements d'images du navigateur vers Cloudinary .

REMARQUE : je ne suis pas intéressé par la bibliothèque Cloudinary JS , car elle dépend de jQuery et mon application ne le fait pas. Je ne suis intéressé que par le traitement de flux nécessaire pour ce faire avec le javascript natif et le fetchpolyfill de Github .


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

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

Réponses:

44

Les flux commencent à débarquer sur la plate-forme Web ( https://jakearchibald.com/2016/streams-ftw/ ) mais il n'en est encore qu'à ses débuts.

Bientôt, vous serez en mesure de fournir un flux comme corps d'une requête, mais la question ouverte est de savoir si la consommation de ce flux est liée aux octets téléchargés.

Des redirections particulières peuvent entraîner la retransmission des données vers le nouvel emplacement, mais les flux ne peuvent pas «redémarrer». Nous pouvons résoudre ce problème en transformant le corps en un rappel qui peut être appelé plusieurs fois, mais nous devons nous assurer que le fait d'exposer le nombre de redirections n'est pas une fuite de sécurité, car ce serait la première fois que JS pourrait détecter ça.

Certains se demandent s'il est même judicieux de lier la consommation de flux aux octets téléchargés.

En bref: ce n'est pas encore possible, mais à l'avenir, cela sera géré soit par des flux, soit par une sorte de rappel de niveau supérieur transmis fetch().

JaffaTheCake
la source
7
Dommage. Accepter cela pour le moment, mais quand cela deviendra une réalité, j'espère que quelqu'un d'autre publiera une solution mise à jour! :)
neezer
1
Mise à jour - affichage de la progression avec Fetch API à l'aide de flux - twitter.com/umaar/status/917789464658890753/photo/1
Eitan Peer
2
@EitanPeer Nice. Une chose similaire fonctionnera-t-elle pour le téléchargement, par exemple POST?
Michael
4
@EitanPeer Mais, la question concerne la progression du téléchargement, pas du téléchargement
John Balvin Arias
1
nous
sommes en
24

Ma solution est d'utiliser axios , qui prend en charge cela assez 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,
        //})
      })

J'ai un exemple d'utilisation de ceci dans react on github.

dwjohnston
la source
2
C'était aussi ma solution. Axios semble bien s'adapter au moule.
Jason Rice
1
Est -ce que l' axiosutilisation fetchou XMLHttpRequestsous le capot?
Dai
3
XMLHttpRequest. Si vous utilisez ceci pour react native, méfiez-vous que XMLHttpRequest semble être TRÈS TRÈS lent à analyser les grandes réponses json par rapport à fetch (environ 10 fois plus lent, et il gèle tout le thread d'interface utilisateur).
Cristiano Coelho
22
Ne répond pas à la question! Si la question est "comment faites-vous x en y?" dire "faire x en z à la place" n'est pas une réponse acceptable.
Derek Henderson le
4
Cela ne répond pas à la question, surtout parce axiosqu'il ne s'utilise pas fetchsous le capot, et n'a pas un tel support. Je suis littéralement en train de l' écrire maintenant pour eux.
sgammon le
7

Je ne pense pas que ce soit possible. Le projet déclare:

il fait actuellement défaut [ par rapport à XHR ] pour demander une progression


(ancienne réponse):
Le premier exemple du chapitre Fetch API donne un aperçu de la façon de:

Si vous souhaitez recevoir progressivement les données corporelles:

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

Outre leur utilisation de l' Promiseanti - modèle du constructeur , vous pouvez voir queresponse.body s'agit d'un Stream à partir duquel vous pouvez lire octet par octet en utilisant un lecteur, et vous pouvez déclencher un événement ou faire ce que vous voulez (par exemple enregistrer la progression) pour chacun d'eux.

Cependant, la spécification Streams ne semble pas être tout à fait terminée, et je n'ai aucune idée si cela fonctionne déjà dans une implémentation de récupération.

Bergi
la source
11
Si je lis correctement cet exemple, ce serait pour télécharger un fichier via fetch. Je suis intéressé par les indicateurs de progression pour le téléchargement d' un fichier.
neezer le
Oups, cette citation parle de la réception d' octets, ce qui m'a dérouté.
Bergi
@Bergi Remarque, le Promiseconstructeur n'est pas nécessaire. Response.body.getReader()renvoie un Promise. Voir Comment résoudre Uncaught RangeError lors du téléchargement de json de grande taille
guest271314
3
@ guest271314 ouais, je l'ai déjà corrigé à la source de la citation. Et non, getReaderne retourne pas une promesse. Aucune idée de ce que cela a à voir avec le message que vous avez lié.
Bergi
@Bergi Oui, vous avez raison. .getReader()La .read()méthode de retour a Promise. C'est ce que nous essayions de transmettre. Le lien fait allusion à la prémisse que si la progression peut être vérifiée pour le téléchargement, la progression peut être vérifiée pour le téléchargement. Mettre en place un modèle qui renvoie le résultat attendu, à un degré appréciable; c'est la progression du fetch()téléchargement. Je n'ai pas trouvé de chemin vers echoun objet Blobou Fileà jsfiddle, manquant probablement quelque chose de simple. Test des localhostfichiers téléchargés très rapidement, sans imiter les conditions du réseau; bien que juste rappelé Network throttling.
invité271314
6

Mise à jour: comme le dit la réponse acceptée, c'est impossible maintenant. mais le code ci-dessous a géré notre problème pendant un certain temps. Je dois ajouter qu'au moins nous avons dû passer à l'utilisation d'une bibliothèque basée sur 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');
}

grâce à ce lien: https://jakearchibald.com/2016/streams-ftw/

Hosseinmp76
la source
2
Bien, mais est-ce que cela s'applique également aux téléchargements?
noyau le
@kernel J'ai essayé de le découvrir mais je n'ai pas pu le faire. et j'aime trouver un moyen de le faire pour le téléchargement aussi.
Hosseinmp76
Même chose, mais jusqu'à présent, je n'ai pas eu trop de chance de trouver / créer un exemple de téléchargement fonctionnel.
noyau
1
content-length! == longueur du corps. Lorsque la compression http est utilisée (courante pour les gros téléchargements), la longueur du contenu est la taille après la compression http, tandis que la longueur est la taille après l'extraction du fichier.
Ferrybig le
@Ferrybig Je n'ai pas compris votre point. ai-je utilisé l'égalité quelque part?
Hosseinmp76
4

Puisque aucune des réponses ne résout le problème.

Juste pour des raisons de mise en œuvre, vous pouvez détecter la vitesse de téléchargement avec un petit morceau initial de taille connue et le temps de téléchargement peut être calculé avec la longueur du contenu / la vitesse de téléchargement. Vous pouvez utiliser ce temps comme estimation.

Shishir Arora
la source
3
Très astucieux, belle astuce à utiliser en attendant une solution temps réel :)
Magix
14
Trop risqué pour moi. Je ne voudrais pas finir comme la barre de progression du fichier de copie de Windows
Jack Giffin
2

Une solution de contournement possible serait d'utiliser new Request() constructeur puis de vérifier l' Request.bodyUsed Booleanattribut

le bodyUsed getter de l'attribut doit retourner true si disturbedet false dans le cas contraire.

pour déterminer si le flux est distributed

Un objet implémentant le Bodymixin est dit disturbedif bodyest non nul et sonstream est disturbed.

Renvoie l' appel de l' fetch() Promiseintérieur .then()enchaîné à l' .read()appel récursif d'unReadableStream quand Request.bodyUsedest égal à true.

Notez que l'approche ne lit pas les octets de Request.bodycar les octets sont transmis au point de terminaison. En outre, le téléchargement peut se terminer bien avant que toute réponse ne soit renvoyée dans son intégralité au navigateur.

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

}
invité271314
la source
-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}%`;
}
Léon Gilyadov
la source
Je tiens à rendre hommage à Benjamin Gruenbaum pour toute la réponse. Parce que je l'ai appris de sa conférence.
Leon Gilyadov
@LeonGilyadov La conférence est-elle disponible en ligne n'importe où? Un lien vers la source serait bien.
Mark Amery
@MarkAmery Le voici: youtube.com/watch?v=Ja8GKkxahCo (la conférence a été donnée en hébreu)
Leon Gilyadov
11
La question concerne le téléchargement et non le téléchargement.
sarneeh
le problème avec la progression de la récupération est lorsque vous voulez télécharger (il n'y a pas de problème avec le téléchargement)
Kamil Kiełczewski
-5

L'élément clé est ReadableStreamobj_response .body≫.

Échantillon:

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

Vous pouvez tester son exécution sur une grande page, par exemple https://html.spec.whatwg.org/ et https://html.spec.whatwg.org/print.pdf . CtrlShiftJ et chargez le code dans.

(Testé sur Chrome.)

Pacerier
la source
1
Cette réponse obtient moins de points mais personne n'explique pourquoi donner un point moins - alors je donne +1
Kamil Kiełczewski
4
Il obtient un -1 de ma part car ce n'est pas pertinent pour le téléchargement .
Brad