下载大型base64文件时IE11挂起

时间:2017-09-08 11:29:05

标签: javascript jquery internet-explorer blob internet-explorer-11

对于此代码中的IE11,base64文件将转换为Blob,然后创建下载链接。但是使用大型base64文件(〜> 5Mb),浏览器会在创建Blob时挂起:

new Blob(byteArrays, {type: contentType});

如何解决这个问题?

var fullFileName = 'example.test';
var b64file = '';
var contentType = 'application/octet-stream';

b64toBlob(b64file, contentType, 512, function(blob){
  if (typeof MouseEvent != "function") {  //for IE
    $('#ie_download').off('click').on('click', function(){
      window.navigator.msSaveBlob(blob, fullFileName);
    })
      .show();
    success();
    return;
  }
  //other browsers
  var blobUrl = URL.createObjectURL(blob);
  var jqLink = $('<a style="display: none" target="_blank">Save</a>');
  $('#download')
    .attr('download', fullFileName)
    .attr('href', blobUrl)
    .show();
  success();
});

function success () {
  $('#waiting').hide();
}

function b64toBlob(b64Data, contentType, sliceSize, resultCb) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  var offset = 0;
  setTimeout(function generateByteArrays () {
    var slice = byteCharacters.slice(offset, offset + sliceSize);
    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    var byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);

    offset += sliceSize;
    if (offset < byteCharacters.length) {
      setTimeout(generateByteArrays, 0);
    }
    else {
      resultCb(new Blob(byteArrays, {type: contentType}));
    }
  }, 0);
}
#download, #ie_download {
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='waiting'>waiting...</div>
<a id='download'>Save</a>
<a id='ie_download'>IE Save</a>

1 个答案:

答案 0 :(得分:3)

<强>更新

我刚注意到段大小为512 bytes 。这非常小,并且将使用5 MB文件创建10,240个数组切片,IE似乎执行非常慢的操作(即创建缓冲区,复制内容,检查下一个切片,创建旧大小的新缓冲区和下一个切片的大小,复制旧缓冲区+新内容等。)。

你应该能够使用至少1000倍的切片大小(0.5 mb)并且不会阻止IE11。

使用较大切片大小的原始代码进行演示:

setTimeout(test, 10);

function test() {
  // Generate a big base-64 string, chop off data-uri
  // NOTE: Initial creation will take a couple of seconds...
  var c = document.createElement("canvas"); c.width = c.height = 6000;
  var ctx = c.getContext("2d"); // create some lines to degrade compression ratio...
  for(var i = 0, r = Math.random.bind(Math), w = c.width, h = c.height; i < 500; i++) {
    ctx.moveTo(r()*w, r()*h);ctx.lineTo(r()*w, r()*h);
  }
  ctx.stroke();
  var base64 = c.toDataURL();
  base64 = base64.substr(base64.indexOf(",")+1);

  // OK, now we have a raw base64 string we can use to test
  document.querySelector("out").innerHTML = "Converting...";

  // Increase sliceSize by x1024
  b64toBlob(base64, "application/octet-stream", 512<<10, function(blob) {
    document.querySelector("out").innerHTML = "Blob size: " + blob.size;
  });

  function b64toBlob(b64Data, contentType, sliceSize, resultCb) {
    contentType = contentType || '';
    sliceSize = sliceSize || (512<<10);

    var byteCharacters = atob(b64Data);
    var byteArrays = [];

    var offset = 0;
    setTimeout(function generateByteArrays () {
      var slice = byteCharacters.slice(offset, offset + sliceSize);
      var byteNumbers = new Array(slice.length);
      for (var i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }
      var byteArray = new Uint8Array(byteNumbers);
      byteArrays.push(byteArray);

      offset += sliceSize;
      if (offset < byteCharacters.length) {
        setTimeout(generateByteArrays, 5);
      }
      else {
        resultCb(new Blob(byteArrays, {type: contentType}));
      }
    }, 5);
  }
}
<out>Creating test data...</out>

由于IE11中的bug,不能将XMLHttpRequest()与data-uri和响应类型“blob”一起使用,否则您可以使用它来为您执行所有这些操作。

var c = document.createElement("canvas"); c.width = c.height = 4000;
var ctx = c.getContext("2d"); // create some lines to degrade compression ratio...
for(var i = 0, r = Math.random.bind(Math), w = c.width, h = c.height; i < 200; i++) {
  ctx.moveTo(r()*w, r()*h);ctx.lineTo(r()*w, r()*h);
}
ctx.stroke();
var base64 = c.toDataURL();
base64 = base64.substr(base64.indexOf(",")+1);

b64toBlob(base64, "application/octet-stream", function(blob) {
  console.log(blob)
})

// Using XMLHttpRequest to do the work (won't work in IE11...)
function b64toBlob(base64, mimeType, callback) {
  var xhr = new XMLHttpRequest();
  xhr.responseType = "blob";
  xhr.onload = function() {
    callback(this.response)
  };
  xhr.open("GET", "data:" + mimeType + ";base64," + base64);
  xhr.send();
}

旧答案(仍然适用/推荐)

将超时时间增加到7-10ms,并查看是否解除阻塞循环(如果仍然阻塞,则使用更高的值)。

超时为0实际上超出了此异步分段方法的目的。