我有两个基于Axios的函数-downloadContentuploadContent

downloadContent函数以流的形式返回数据,uploadContent函数使用该流上传数据.

这个 idea 实质上是在两个后端之间传输数据.但由于有些文件可能有点大(5-10 GB+),我不想下载内存中的文件,然后再上传.

但这正是我在下面的方法/代码中观察到的-进程内存使用量不断上升,直到达到文件大小(Ish).

async function downloadContent(downloadUrl) {
  return await axios
    .get(downloadUrl, {
      responseType: "stream",
    })
    .then((r) => ({
      file: r.data,
      someOtherProps: {},
    }));
}

async function uploadContent() {
  const download = await downloadContent("download/url");

  return await axios.post("upload/url", download.file, {
    headers: {
      "Content-Type": "specific-content-type",
    },
  });
}

await uploadContent();

是我做错了什么事吗?

一般来说,我如何在两个服务器之间实现流,以最大限度地减少使用Axios的内存占用?

推荐答案

流可能是在内存中缓冲的,这就是您观察到内存使用率增加的原因.

在原始代码中,您有downloadContent函数,该函数以流的形式获取数据并返回.但是,当您调用uploadContent函数时,您将把流直接传递到Axios post方法.

return await axios.post("upload/url", download.file, {
    headers: {
      "Content-Type": "specific-content-type",
    },
});

The Axios library, by default, buffers the entire input before making the HTTP request. When you pass the stream directly as the data parameter (download.file) to the axios.post method, Axios waits for the entire stream to be consumed (buffered in memory) before it actually makes the HTTP request.
This is because Axios is designed to work with both browsers and Node.js, and in a browser environment, streams cannot be sent as request data directly.
Therefore, Axios buffers the stream in memory to ensure compatibility across environments. This is what leads to high memory usage for large files.

另外,你可以拿到transform request data before it is sent to the server美元.同样,完整的请求被缓冲在内存中.


为了避免在内存中缓冲整个文件,您可以使用流作为管道在下载文件时上载文件.这样,您实质上就是在传递数据,而不是保留它.这可以通过使用流上可用的pipe方法来实现.

const axios = require("axios");
const stream = require("stream");

async function downloadContent(downloadUrl) {
  return axios
    .get(downloadUrl, { responseType: "stream" })
    .then((response) => response.data);
}

async function uploadContent(uploadUrl, downloadStream) {
  return new Promise((resolve, reject) => {
    downloadStream.pipe(
      new stream.PassThrough().on("error", reject).pipe(
        axios
          .post(uploadUrl, downloadStream, {
            headers: {
              "Content-Type": "specific-content-type",
            },
          })
          .then(resolve)
          .catch(reject)
      )
    );
  });
}

(async () => {
  try {
    const downloadStream = await downloadContent("download/url");
    await uploadContent("upload/url", downloadStream);
    console.log("Upload successful.");
  } catch (error) {
    console.error("An error occurred:", error);
  }
})();

现在,downloadContent函数直接返回从axios获得的流.

uploadContent函数中,流通过PassThrough流直接输送到axios POST请求.这意味着,当数据被下载时,它就被上传,而不是在内存中缓冲.

现在,uploadContent函数返回一个promise ,该promise 在上传完成时进行解析,或者在出现错误时拒绝.

最后一个调用被包装在try-Catch块中,以处理下载或上载过程中可能发生的任何错误.

这应该有助于在使用Axios的两台服务器之间传输大文件时最大限度地减少内存占用.


关键是使用pipe method on the streamPassThrough stream来处理块中的数据,而不是一次缓冲所有数据.

async function uploadContent(uploadUrl, downloadStream) {
  return new Promise((resolve, reject) => {
    downloadStream.pipe(
      new stream.PassThrough().on("error", reject).pipe(
        axios
          .post(uploadUrl, downloadStream, {
            headers: {
              "Content-Type": "specific-content-type",
            },
          })
          .then(resolve)
          .catch(reject)
      )
    );
  });
}

pipe方法是Node.js Streams的一个特性,它允许您将streamlink 在一起.当您将一个流转换为另一个流时,数据将以小块的形式从源流流入目标流.这样,您就不必在内存中缓冲文件的全部内容.PassThrough流是一个简单的流,它除了将数据从源传递到目标之外什么也不做.在这里使用它是为了方便配管过程.

通过使用pipe,您实际上是在告诉程序在下载数据时以块的形式传递数据.这样,Axios将能够以块的形式发送数据,而不是等待整个流在内存中进行缓冲.

请注意,更新后的代码可能无法在浏览器环境中运行,因为它依赖于Node.js流特性.它适用于服务器端Node.js环境,在该环境中您可以更好地控制流和网络请求.这种方法利用Node.js流来最大限度地减少传输大文件时的内存使用.

Javascript相关问答推荐

使用jsPDF添加Image JPEG将p5.js草图画布下载为PDF

如何在Jest中调用setupFile下列出的文件所需的函数?

如何使用CSS和JavaScript创建粘性、凝聚力的形状到形状(容器)变形?

如何避免使用ajax在Vue 3合成API中重定向

使用JavaScript重命名对象数组中的键

如何保持子画布元素的1:1宽高比?

在JS中获取名字和姓氏的首字母

可以的.是否可以在不预编译的情况下使用嵌套 Select 器?

如何使用Echart 5.5.0创建箱形图

按下同意按钮与 puppeteer 师

为什么promise对js中的错误有一个奇怪的优先级?

为什么Mutations 观察器用微任务队列而不是macrotask队列处理?

获取Uint8ClampedArray中像素数组的宽度/高度

使用GraphQL查询Uniswap的ETH价格

如何避免页面第一次加载时由于CSS样式通过JavaScript更改而出现闪烁

用于在路径之间移动图像的查询

从逗号和破折号分隔的给定字符串中查找所有有效的星期几

Node.js API-queryAll()中的MarkLogic数据移动

react 路由如何使用从加载器返回的数据

ReferenceError:无法在初始化之前访问setData