javascriptblobjszipfilesaver.js

Why FileSaver saveAs won't work with a JSZip?


First time posting, this is the whole code (majority of it I found online and tweaked some things to serve my purpose), but more specifically, my error is towards the end where I get.

Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.

When I simply use saveAs(img_url, "img.png"), the option to save to laptop pops up. But I get the error I mentioned above when trying to use "content". I have filesaver and jszip in the script, I just can't seem to find any way to fix the error, which then stops executing anything more. Sorry for messy code, would really appreciate help.

Main part is towards the bottom, the rest is there just incase someone might want to see. Theres the url to blob then the canvas generator, I just don't know why it won't save.

!function() {
    function dataURLtoBlob(dataURL, type) {
      var binary = atob(dataURL.split(',')[1]),
          length = binary.length,
          binaryArray = new Uint8Array(length);
      for (var i = 0; i < length; i++) {
        binaryArray[i] = binary.charCodeAt(i);
      }
      return new Blob([binaryArray], {type: type});
    }

    var SolidImage = function() {
      var canvas = document.createElement('canvas'),
          ctx = canvas.getContext('2d');
      this.img = new Image();
      this.make = function(color) {
        canvas.width = 500;
        canvas.height = 500;
        
        ctx.fillStyle = color;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "#FFFFFF";
        ctx.textAlign = "center";
        ctx.font = "bold 50px Courier New";
        ctx.fillText(color.substring(3), 250, 250);
        var dataURL = canvas.toDataURL('image/png')
        this.img.src = dataURL;
        if (this.blobURL) URL.revokeObjectURL(this.blobURL);
        this.blob = dataURLtoBlob(dataURL, 'image/png');
        this.blobURL = URL.createObjectURL(this.blob);
      }
    };
    
    var solidImage = new SolidImage(),
        button = document.getElementById('make'),
        result = document.getElementById('result'),
        link = document.createElement('a');
    
    link.setAttribute('target', '_blank');
    result.appendChild(solidImage.img);
    result.insertAdjacentHTML('beforeend', 'Save this image or<br>');
    result.appendChild(link);
    solidImage.img.width = 600;
  
    
    button.addEventListener('click', function(){
        var zip = new JSZip();
        console.log("after zip");
        //var img = zip.folder("rdm_imgs");
        //////////////////////////////////
        for (var i = 0; i < 1; i++) {
            setTimeout(function() {
        var rgb_r = Math.floor(Math.random() * (256+1)),
            rgb_g = Math.floor(Math.random() * (256+1)),
            rgb_b = Math.floor(Math.random() * (256+1)),
            random_color = "rgb(" + rgb_r + ", " + rgb_b + ", " + rgb_g + ")";
      var filename = random_color.replace(/\s/g, "") + '.png';
      solidImage.make(random_color);
      link.innerHTML = 'Download content ' + filename;
      var img_url = solidImage.blob;
      //console.log(img_url.replace(/^data:image\/(png|jpg);base64,/, ""));
      console.log(img_url);
      //link.setAttribute('href', img_url);
      //link.setAttribute('download', filename);
      result.className = 'generated';

      zip.file(filename, img_url);
            },i * 500)}
        console.log("after loop");
        var content = zip.generateAsync({type:"blob"});
        console.log("after zip generate");
        saveAs(content, "imgs.zip");
        console.log("after saveAs");
        //link.innerHTML = 'Download Contents.zip';
        //var img_url = solidImage.blobURL;
        //link.setAttribute('href', content);
        //link.setAttribute('download', "content.zip");
    });
  }();

Solution

  • zip.gzip.generateAsync() returns a Promise. This Promise will resolve with a Blob, some times later, but it's a Promise, not a Blob.
    So you need to await the resolution of this Promise to access the generated Blob.

    You can either mark your function as async and then use the await keyword:

    button.addEventListener('click', async function(){
      // ...
      var content = await zip.generateAsync({type:"blob"});
    

    Or wrap the saveAs part in a callback passed to the Promise's .then():

    zip.generateAsync({type:"blob"}).then(function(content) {
      console.log("after zip generate");
      saveAs(content, "imgs.zip");
    })
    

    Now, whatever you choose, your zip file will actually be empty. You add content to it only in the callback of setTimeout, meaning that this content will be added only after you did create the zip file, which is too late.
    So remove the setTimeout( part which seems useless and execute its callback's content directly.