将Uint8Array转换为大文件使浏览器崩溃

3

有一个应用程序,其中有一个类型为“file”的输入。以下方法获取文件,然后通过AJAX准备将其发送到服务器。

private StartUpload = (files) => {
    if (files && files.length === 1) {
        this.GetFileProperties(files[0])
            .done((properties: IFileProperties) => {
                $('input[type=file]').val("");
                if (this._compatibleTypes.indexOf(properties.Extension) >= 0) {
                    var base64 = this.ArrayBufferToBase64(properties.ArrayBuffer);

                    this.DoFileUpload(base64, properties.Extension).always(() => {
                        this.ShowDialogMessage('edit_document_upload_complete', 'edit_document_upload_complete');
                    });
                } else {
                    this.ShowDialogMessage('edit_document_upload_incompatible', 'edit_document_upload_compatible_types', this._compatibleTypes);
                }
            });
    } else {
        this.ShowDialogMessage('edit_document_upload_one_file', 'edit_document_upload_one_file_msg');
    }
};

private ArrayBufferToBase64(buffer): any {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    for (var xx = 0, len = bytes.byteLength; xx < len; xx++) {
        binary += String.fromCharCode(bytes[xx]);
    }
    return window.btoa(binary);
}

private DoFileUpload = (base64, extension) => {
    this.IsLoading(true);
    var dfd = $.Deferred();

    var data = {
        data: base64
    };

    UpdateFormDigest((<any>window)._spPageContextInfo.webServerRelativeUrl, (<any>window)._spFormDigestRefreshInterval);

    var methodUrl = "_vti_bin/viewfile/FileInformation.asmx/AddScannedItemAlt";

    $.ajax({
        headers: {
            "X-RequestDigest": $("#__REQUESTDIGEST").val()
        },
        url: methodUrl,
        contentType: "application/json",
        data: JSON.stringify(data),
        dataType: 'json',
        type: "POST",
        success: (response) => {
            // do stuff
        },
        error: (e) => {
            // do stuff
        }
    });

    return dfd;
};

这个方法在绝大多数情况下都能完美运行。但是,当文件大小较大(比如200MB以上),它会导致浏览器崩溃。

  • 在Chrome中,页面会变成黑灰色,并显示"aw snap"信息,最终浏览器崩溃。

  • 在IE中,控制台会显示"Out of Memory"错误,但浏览器仍然可以工作。

  • 在FF中,会弹出"Unresponsive script"警告。选择"don't show me again"后,脚本会一直运行,直到控制台报错"out of memory"。

以下是脚本崩溃的位置:

for (var xx = 0, len = bytes.byteLength; xx < len; xx++) {
    binary += String.fromCharCode(bytes[xx]);
}

尝试在此处添加try/catch并没有起到任何作用,也没有捕获错误。

我可以进入循环而不崩溃,但是由于len = 210164805,每次迭代都需要逐步调试非常困难。因此,我尝试向循环中添加console.log(xx)并让其运行 - 但是在日志中出现任何内容之前,浏览器就已经崩溃了。

是否有某个字符串大小的限制,一旦超过该限制,浏览器就会崩溃呢?

谢谢

1个回答

0

你需要以异步方式完成这个任务,可以通过将代码分块或按时间段执行来实现。

这意味着你的代码需要使用回调函数,但除此之外很简单 -

示例

var bytes = new Uint8Array(256*1024*1024);  // 256 mb buffer

convert(bytes, function(str) {              // invoke the process with a callback defined
   alert("Done!");
});

function convert(bytes, callback) {

  var binary = "", blockSize = 2*1024*1024, // 2 mb block
      block = blockSize,                    // block segment
      xx = 0, len = bytes.byteLength;
  
  (function _loop() {
    while(xx < len && --block > 0) {        // copy until block segment = 0
      binary += String.fromCharCode(bytes[xx++]);
    }
    
    if (xx < len) {                         // more data to copy?
      block = blockSize;                    // reinit new block segment
      binary = "";                          // for demo to avoid out-of-memory
      setTimeout(_loop, 10);                // KEY: async wait
      
      // update a progress bar so we can see something going on:
      document.querySelector("div").style.width = (xx / len) * 100 + "%";
    }
    else callback(binary);                  // if done, invoke callback
  })();                                     // selv-invoke loop
}
html, body {width:100%;margin:0;overflow:hidden}
div {background:#4288F7;height:10px}
<div></div>

使用大缓冲区转换为字符串可能会导致客户端内存耗尽。200mb的缓冲区转换为字符串将添加2 x 200mb,因为字符串以UTF-16(即每个字符2个字节)存储,因此这里我们使用了600 mb的空间。
这取决于浏览器和它如何处理内存分配,当然还有系统。浏览器将尝试防止恶意脚本填满内存的情况。
你应该能够留在ArrayBuffer并将其发送到服务器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接