1

I' using JSzip to create zipfile which contain all the images files. I got the images from external links within a loop using XMLHttpRequest. According to my code zipfile create before complete the XMLHttpRequest. So it returns empty zip file. How to create zip file after looping all the files?

$(document).on('click', '.download', function(){ 
    var path = $(this).attr("data-id");
    var count = $(this).attr("value");
    var storageRef = firebase.storage().ref();
    var zip = new JSZip();
    console.log(count);

    for (i = 1; i <= count; i++) { 
        console.log(path+i+".png");
        var imagePath = path+i+".png";

        // Create a reference to the file we want to download
        var starsRef = storageRef.child(imagePath);

        starsRef.getDownloadURL().then(function(url) {
        // Insert url into an <img> tag to "download"
        ImageUrl = url;

        var xhr = new XMLHttpRequest();
        xhr.open('GET', ImageUrl, true);
        xhr.responseType = "arraybuffer";
        xhr.onreadystatechange = function(evt) {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {

                    zip.file(i+".png", xhr.response);

                }
            }
        };
        xhr.send();
      })
    }
    zip.generateAsync({type:"blob"})
    .then(function(content) {
        // see FileSaver.js
        saveAs(content, "my.zip");
    });
});
Bandara
  • 283
  • 2
  • 4
  • 17
  • 1
    Seems like you are calling `zip.generateAsync` before the loop is done so it could execute before you call `zip.file(i+".png", xhr.response);` for the first time. I assume if you wrap the loop into a `promise` you can chain `zip.generateAsync` as needed. This might help ► [How to return many Promises in a loop and wait for them](http://stackoverflow.com/questions/31426740/how-to-return-many-promises-in-a-loop-and-wait-for-them-all-to-do-other-stuff) – Nope Jan 06 '17 at 10:17
  • Yes. That is the problem as I stated in my question. The problem is I don't have any idea to overcome the situation. I think this is slimier to ajax request. – Bandara Jan 06 '17 at 10:21
  • In addition to @Fran s comment - searching SO with some keywords like [wait for async calls loop javascript](http://stackoverflow.com/search?q=wait+for+async+calls+loop+javascript) gives a lot of results how this can be done. – michaPau Jan 06 '17 at 10:33
  • 1
    @Bandara I linked an SO post that shows you how to re-write your loop using a promise. That should show you how to overcome the problem. – Nope Jan 06 '17 at 11:19

1 Answers1

2

JSZip supports promises as content: you can wrap each HTTP calls into promises and not explicitly wait.

The first function, downloadUrlAsPromise, wraps the xhr call into a Promise. The second function, downloadFirebaseImage, chains the promise from getDownloadURL with the promise of the first function. The result is a promise of the xhr content.

Once you have that, you can give directly the promise to JSZip like that:

zip.file(i+".png", downloadFirebaseImage(imagePath));

Full methods:

function downloadUrlAsPromise (url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = "arraybuffer";
    xhr.onreadystatechange = function(evt) {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.response);
        } else {
          reject(new Error("Ajax error for " + url + ": " + xhr.status));
        }
      }
    });
    xhr.send();
  });
}

function downloadFirebaseImage(storageRef, path) {
  var starsRef = storageRef.child(imagePath);
  return starsRef.getDownloadURL().then(function(url) {
    return downloadUrlAsPromise(url);
  });
}


// ...
for (i = 1; i <= count; i++) { 
  console.log(path+i+".png");
  var imagePath = path+i+".png";
  zip.file(i+".png", downloadFirebaseImage(imagePath));
}

zip.generateAsync({type:"blob"})
.then(function(content) {
  // see FileSaver.js
  saveAs(content, "my.zip");
});
David Duponchel
  • 3,959
  • 3
  • 28
  • 36