5

I wrote the following function to process an AJAX request to fetch data:

var xhr = createCORSRequest('GET', url);
if (!xhr) {
    alert('CORS not supported');
    return;
}
xhr.onload = function() {
    var txt1 = xhr.responsetxt1;
    var heading = getheading(txt1);

    if (heading == 'PASS') {
        var file = "My_URL" + ".js";
        downloadFile(file);
        //My code to display a progress bar here?
    } else {
        //Logic to handle failure to load
    }
};

Here is my downloadFile function to download the file. But, I don't understand how to:

  • Check if the download completed.
  • Display a progress bar to show the progress.

If you can add a description of how it works, that'd be great. Thanks.

function downloadFile(fileName) {
    (function(d) {
        var ref = d.getElementsByTagName('script')[0];
        var js = d.createElement('script');
        js.src = fileName;
        ref.parentNode.insertBefore(js, ref);
        // My code to display a progress bar here?
    }(document));
}
informatik01
  • 16,038
  • 10
  • 74
  • 104
Zac
  • 695
  • 1
  • 11
  • 34
  • looks like https://stackoverflow.com/questions/18126406/how-can-i-get-the-progress-of-a-downloading-script – Josh Lin Jun 28 '17 at 11:44

3 Answers3

3

AFAIK, script elements don't have progress events. Your best bet is to use an XHR to get the script's body, then count on the browser cache for a second fetch. The problem is that your script then needs to be parsed by the browser, and there doesn't seem to be events for that.

My solution is pure JS, so you can adapt it to whatever framework you're using. It assumes that actual download will be about 70% of the total time, and allocates 20 % to the browser parsing. I use a non-minified versionof the awesome three.js 3D library as a biggish source file.

because it is in another sandbox, progress callculation is inaccurate, but if you serve your own script that shouldn't be a problem.

keep in mind that this is a fairly stripped down implementation. I used a simple HR as a progress bar, for example.

//this is a rough size estimate for my example file
let TOTAL_ESTIMATE = 1016 * 1024;
// I use a hr as a 
let bar = document.getElementById("progressbar");
let button = document.getElementById("dlbtn");

var js; // to hold the created dom element
var fileName; // to hold my cacheBusted script adress

/* this function will be called several times during (the first) download, with info about how much data is loaded */

function onProgress(e) {
  var percentComplete = e.loaded / TOTAL_ESTIMATE;
  if (e.lengthComputable) {
    percentComplete = e.loaded / e.total;
  }
  p = Math.round(percentComplete * 100);
  console.log("progress", p + "%,", e.loaded, "bytes loaded")
  bar.style = "width: " + (5 + .6 * p) + "%"; // I just assume dl will be around 60-70% of total time

}

/* this function is called when info comes. at the end of the initial download, the readystate will be 4 so we then set the file's src attribute, triggering a re-download but taking advantage of the browser's cache. It's not ideal, and simply `eval` ing the data would probably yield better results. I just assumed you wanted a <script> tag on your page, and for it to be evaluated. */ 
function onReadyState(e) {
  let r = e.target;
  //this is lifted almost verbatim from http://vanilla-js.com/ ;)
  if (r.readyState != 4 || r.status != 200)
    return;
  let l = r.responseText.length;
  console.log("Success !", l, "bytes total (" + Math.round(l / 1024) + " KB )");
  bar.style = "width: 70%";
  //just add / to next line to toggle ending methods
  /* you could speed up the proces by simply eval()ing the returned js. like so (please be aware of security concerns) :
  eval.bind(window)(r.responseText);
  onScriptLoaded();
  /*/

  js.src = fileName;
  bar.style = "width: 80%";
  var ref = document.getElementsByTagName('script')[0];
  ref.parentNode.insertBefore(js, ref);
  //*/
  
};

//this is called when the script has been evaluated :
function onScriptLoaded() {
  bar.style = "width: 100%; background-color: lightgreen;";
  button.disabled = false;
  console.log("script has been evaluated ?", THREE ? "yes" : "no"); // the demo file exposes window.THREE
}

function downloadFile(file) {
  button.disabled = true;
  (function(d) {
    // this helps to test this script multiple times. don't keep it
    fileName = file + "?bustCache=" + new Date().getTime();


    console.log("inserting new script");
    js = d.createElement('script');
    js.type = "text/javascript";
    js.defer = "defer";
    js.async = "async";
    var r = new XMLHttpRequest();
    bar.style = "width: 5%"; //always react ASAP
    r.addEventListener("progress", onProgress);
    r.open("GET", fileName, true);
    r.onreadystatechange = onReadyState;
    js.onload = onScriptLoaded;
    r.send();
    // My code to display a progress bar here?
  }(document));
}
#progressbar {
  height: 6px;
  border-radius: 3px;
  width: 0%;
  border-color: green;
  background-color: green;
}
<button onclick="downloadFile('https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js', this)" id="dlbtn">Download</button>
<script id="dummy" type="text/javascript">
  console.log("dummy script ready")
</script>
<hr id="progressbar" align="left" />
Lampe2020
  • 115
  • 1
  • 12
Boris
  • 1,161
  • 9
  • 20
  • Can you please add some description? I am unable to understand. Is this an asynchronous download? How do I do a synchronous download, i.e. wait till the download is complete, to load the file? At the moment, in slow networks file downloads, but takes too long to load and the e.lengthComputable is FALSE (as content-length header is not set on the server). Thanks. – Zac Jun 27 '17 at 23:19
  • 1
    yes, it is an asynchronous download. You can't have a progressbar on synchronous downloads because, well, as they're synchronous, they will block your progressbar :). for the progress to be accurate you need the file size. If the file is always the same, you could use a static size (as I did with TOTAL_ESTIMATE) – Boris Jun 28 '17 at 15:15
  • 1
    I've edited to add comments and a "better" evaluation method... you can try it by copying my snippet and changing the line `/* you could speed ...` into `//* you could speed ...` – Boris Jun 28 '17 at 16:01
  • Thanks, quick question, what does it mean by "script has been evaluated"? Thanks. Also, eval.bind? – Zac Jun 28 '17 at 16:48
  • 1
    We're loading javascript (for my demo Three.js). Once loaded, I've supposed that you wanted for that script to be run ; which is what was done in your script by the two lines `js.src = fileName;` and `ref.parentNode.insertBefore(js, ref);`. Running the loaded script is what I meant by evaluated. My code simply checks that there is indeed a variable named `THREE` after the loading – Boris Jun 28 '17 at 16:52
  • Thanks again. If I can bother you one last time, you mention "this function will be called several times during (the first) download, with info about how much data is loaded" - why does it run several times? Shouldn't it be just once? In my other ProgressBar (with % indicated on a CSS bar), I think it causes it to move back-and-forth a couple of times. Am I right? Why does that happen. Also, is the presence of var THREE a good test, that the script is in memory and will execute on another page? Thanks! – Zac Jun 29 '17 at 03:27
  • I displayed % loaded using the following and it worked; "bar.style = "width: " + (1 * p) + "%"; document.getElementById("myBar").innerHTML = p + "%"; " - hope this is right. – Zac Jun 29 '17 at 04:22
  • Well it seems ok to me. I'm glad it worked ! To answer your last comments, onProgress is called by the browser, when it receives part of your file. Depending on file size and connexion, that may be 0 or many times. We rely on that to calculate the progress. Then I mention (first) download, because `js.src = ...` triggers a second load, this time from the cache. That's why I suggest to use eval instead. – Boris Jun 29 '17 at 05:28
1

DISCLAIMER: I did not test this on Android. I tested ONLY in Chrome (desktop). You should see progress events writing to the browser console

var url = 'url/to/file/';
var request = new XMLHttpRequest();
request.responseType = "blob"; // Not sure if this is needed
request.open("POST", url);

var self = this;
request.onreadystatechange = function () {
    if (request.readyState === 4) {
        var file = $(self).data('file');            
        var anchor = document.createElement('a');
        anchor.download = file;
        anchor.href = window.URL.createObjectURL(request.response);
        anchor.click();            
    }
};

request.addEventListener("progress", function (e) {
    if(e.lengthComputable) {
        var completedPercentage = e.loaded / e.total;
        console.log("Completed: ", completedPercentage , "%");
    }
}, false);
request.send();

Hope this helps.

James Poulose
  • 3,569
  • 2
  • 34
  • 39
0

Well, it depends much more on the programming language instead of client side. For example, PHP has http://php.net/manual/en/session.upload-progress.php

As for client side, Boris answer is a good example! Hope it helps.

Marco
  • 2,757
  • 1
  • 19
  • 24