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

时间:2015-05-01 15:26:29

标签: javascript file input

有一个应用程序,其中输入类型为“文件”。以下方法获取文件,然后准备好通过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显示“无响应脚本”警告。选择“不再显示我”会让它一直运行,直到出现“内存不足”控制台错误。

这就是死亡的地方:

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

将try / catch包裹在此处不会产生任何错误,也不会发现任何错误。

我可以在没有崩溃的情况下进入循环,但是因为len = 210164805,所以逐步完成每次迭代都很困难。为此我尝试将console.log(xx)添加到循环并让它飞行 - 但浏览器崩溃之前任何东西都显示在日志中。

字符串的大小是否有限制可能导致浏览器一旦超出就崩溃?

由于

1 个答案:

答案 0 :(得分: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中并将其发送到服务器。