首页 > 解决方案 > 以块或 ReadableStream 的形式发送 XMLHttpRequest 数据以减少大数据的内存使用

问题描述

我一直在尝试使用 JS 的XMLHttpRequestClass 进行文件上传。我最初尝试过这样的事情:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const rawFileData = await file.arrayBuffer();

request.send(rawFileData);

上面的代码有效(耶!),并将文件的原始二进制数据发送到我的服务器。

但是......它使用了大量内存(因为整个文件都存储在内存中,而 JS 对内存不是特别友好)......我发现在我的机器上(16GB RAM),我不能t 发送大于 ~100MB 的文件,因为 JS 会分配太多内存,并且 Chrome 选项卡会因 SIGILL 代码而崩溃。


所以,我认为在这里使用 ReadableStreams 是个好主意。它在我的情况下具有足够好的浏览器兼容性(https://caniuse.com/#search=ReadableStream),我的 TypeScript 编译器告诉我 request.send(...) 支持 ReadableStreams (我后来得出结论,这是错误的)。我最终得到了这样的代码:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const fileStream = file.stream();

request.send(fileStream);

但是我的 TypeScript 编译器出卖了我(这很痛苦),我在我的服务器 ಠ_ಠ 上收到了“[object ReadableStream]”。

我还没有过多地探索上述方法,所以我不确定是否有办法做到这一点。我也非常感谢这方面的帮助!


将请求拆分成块将是一个最佳解决方案,因为一旦发送了一个块,我们就可以在收到整个请求之前将其从内存中删除。

我已经搜索和搜索,但还没有找到一种方法来做到这一点(这就是我在这里的原因......)。像这样的伪代码将是最佳的:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const fileStream = file.stream();
const fileStreamReader = fileStream.getReader();

const sendNextChunk = async () => {
    const chunk = await fileStreamReader.read();

    if (!chunk.done) { // chunk.done implies that there is no more data to be read
        request.writeToBody(chunk.value); // chunk.value is a Uint8Array
    } else {
        request.end();
        break;
    }
}

sendNextChunk();

我希望此代码以块的形式发送请求,并在发送所有块时结束请求。


我尝试过的最有用的资源,但没有用:

通过 HTTP 将数据从浏览器流式传输到服务器的方法

没有工作,因为:


编辑:我不想使用多部分形式(FormData 类)。我想以块的形式发送从文件流中读取的实际二进制数据。

标签: javascripttypescriptxmlhttprequeststreaming

解决方案


你不能用 XHR afaik 做到这一点。但是更现代的fetchAPIReadableStream确实支持为请求正文传递 a 。在你的情况下:

const file = thisFunctionReturnsAFileObject();

const response = await fetch('/upload-file', {
  method: 'POST',
  body: file.stream(),
});

但是,我不确定这是否真的会使用分块编码


推荐阅读