【问题标题】:Writing File in Chunks using Ajax and FileReader使用 Ajax 和 FileReader 在块中写入文件
【发布时间】:2026-01-04 15:25:06
【问题描述】:

我正在创建一个文件上传器,其中的内容将使用 3rd 方 API 以块的形式写入远程服务器。 API 提供了一个WriteFileChunk() 方法,它接受 3 个参数,目标文件路径、起始位置(Int64)和字节数据(字符串)。

每次 FileReader 接收到最大支持大小 (16kb) 的块时,我需要使用 Ajax 将其传递给 PHP 文件并使用 API 将其写入。我怀疑这应该在 FileReader 的onprogress 事件中完成,但是由于找不到任何类似的例子,我有点不知所措。

使用 FileReader 实现这一点的最佳方法是什么,确保在写入下一个块之前上传每个块?如果onprogress是最好的选择,如何获取当前的chunk数据?

$(document).ready(function()
{
    function uploadFile()
    {
        var files = document.getElementById('file').files;

        if (!files.length)
        {
            alert('Please select a file!');
            return;
        }

        var file = files[0];
        var first_byte = 0;
        var last_byte = file.size - 1;

        // File Reader
        var reader = new FileReader();
        reader.onerror = function(evt)
        {
            switch(evt.target.error.code)
            {
                case evt.target.error.NOT_FOUND_ERR:
                    alert('File Not Found!');
                    break;
                case evt.target.error.NOT_READABLE_ERR:
                    alert('File is not readable');
                    break;
                case evt.target.error.ABORT_ERR:
                    break;
                    default:
                    alert('An error occurred reading this file.');
            };
        };
        reader.onprogress = function(evt)
        {
            if (evt.lengthComputable)
            {
                var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
                console.log(percentLoaded + "%");

                if (percentLoaded < 100)
                {
                    $("#upload_progress").progressbar('value', percentLoaded);
                }
            }
        };
        reader.onabort = function(evt)
        {
            alert('File Upload Cancelled');
        };
        reader.onloadstart = function(evt)
        {
            $("#upload_progress").progressbar({
                value: 0,
                max: 100
            });
        };
        reader.onload = function(evt)
        {
            $("#upload_progress").progressbar('value', 100);
        };
        reader.onloadend = function(evt)
        {
            if (evt.target.readyState == FileReader.DONE) // DONE == 2
            {
                alert("Upload Complete!");
                //console.log(evt.target.result);
            }
        };

        var blob = file.slice(first_byte, last_byte + 1);
        reader.readAsBinaryString(blob);
    }

    fileupload_dialog = $( "#dialog-fileupload" ).dialog(
    {
        autoOpen: false,
        height: 175,
        width: 350,
        modal: true,
        buttons:
        {
            "Upload File": uploadFile
        },
        close: function()
        {
            form[ 0 ].reset();
        }
    });

    form = fileupload_dialog.find( "form" ).on( "submit", function( event )
    {
        event.preventDefault();
        uploadFile();
    });

    $("#file_upload a").click(function()
    {
        event.preventDefault();
        fileupload_dialog.dialog( "open" );
    });
});

【问题讨论】:

    标签: php jquery ajax filereader chunks


    【解决方案1】:

    这里的主要挑战是FileReader 需要先将整个文件读入内存,然后才能通过result 属性将任何可用数据返回给我们,这意味着您不能在文件正在读取(并且进度事件不会提供/指向任何数据):

    这个属性[result]只有在读操作完成后才有效 完成 [...]

    Source

    当完整的文件被加载到内存中时,将读取过程分块(如果可能的话)并没有任何好处,除了可能减少两个进程之间的延迟。

    我会根据上述建议以下方法:

    • 将整个文件加载到内存中,但作为ArrayBuffer
    • 计算所需的段数 (Math.ceil(fileLength/chunkSize))
    • 使用 Uint8Array 视图为 ArrayBuffer 使用偏移量和块长度参数创建块
    • 发送块,异步等待响应,继续下一个块,直到剩下的长度

    如果需要,可以在发送之前将块转换为 Blob:

    var chunkBlob = new Blob([chunk], {type: "application/octet-stream"});
    

    示例流程

    一个伪服务器示例,在每个 16kb 的块之间任意等待 100ms:

    file.onchange = function(e) {
      var fr = new FileReader();
      fr.onprogress = function(e) {progress.value = e.loaded / e.total};
      fr.onload = startUpload.bind(fr);
      progress.style.display = "inline-block";
      fr.readAsArrayBuffer(e.target.files[0]);
    }
    
    // Main upload code
    function startUpload() {
      
      // calculate sizes
      var chunkSize = 16<<10;
      var buffer = this.result;
      var fileSize = buffer.byteLength;
      var segments = Math.ceil(fileSize / chunkSize);
      var count = 0;
      progress.value = 0;
    
      // start "upload"
      (function upload() {
        var segSize = Math.min(chunkSize, fileSize - count * chunkSize);
        if (segSize > 0) {
          var chunk = new Uint8Array(buffer, count++ * chunkSize, segSize); // get a chunk
          progress.value = count / segments;
          // send chunk to server (here pseudo cycle for demo purpose)
          setTimeout(upload, 100); // when upload OK, call function again for the next block
        }
        else {
          alert("Done");
          progress.style.display = "none";
       }
      })()
    }
    body {font:16px sans-serif;margin:20px 0 0 20px}
    <label>Select any file: <input type=file id="file"></label><br>
    <progress id="progress" value=0 max=1 style="display:none" />

    【讨论】: