【问题标题】:Memory usage of downloading files using Fetch API + Blob API使用 Fetch API + Blob API 下载文件的内存使用情况
【发布时间】:2021-08-08 02:32:42
【问题描述】:

我正在使用以下方法下载大量文件,我担心它的内存使用情况。

Chrome's Blob Storage System Design 文档提到以下内容。

如果 blob 的内存空间已满,或者新的 blob 太大而无法在内存中,则 blob 系统会使用磁盘。这可以将旧的 blob 分页到磁盘,或者将新的太大的 blob 直接保存到磁盘。

但是,即使在多次阅读文档后,我仍然有以下顾虑:

  1. 我仍然不确定使用 fetch 会影响此行为并首先将数据加载到内存中。
  2. 如果 fetch 实际上改变了这种行为,是否有推荐用于此方法的文件大小限制(并且不应下载超过该大小的任何文件)?
  3. 在其他(非基于 Chromium 的浏览器)中会有什么行为?
const download = downloadLinks => {

  const _download = async ( downloadLink ) => {

    const blobURL = await fetch(downloadLink, {  
      responseType: 'blob'  
    })
    .then(res => res.blob())
    .then(blob => window.URL.createObjectURL(blob))
 
    const fileName = downloadLink.substr(downloadLink.lastIndexOf('/'))
    
    const a = document.createElement('a')  
    a.href = blobURL
    a.setAttribute('download', fileName)  
    document.body.appendChild(a)  
    a.click()
    a.remove()  
    
    window.URL.revokeObjectURL(blobURL)
  }

  const downloadInterval = () => {

    if (downloadLinks.length == 0) return

    const url = downloadLinks.pop()
    
    _download(url)
    
    if (downloadLinks.length !== 0) setTimeout(downloadInterval, 500)

  }

  setTimeout(downloadInterval, 0)
}

以下是我浏览过的一些资源。这些回答了所有这三个问题的一部分,但我有点太担心如果 Blob 首先加载到内存中,fetch 可能会产生什么影响。

【问题讨论】:

  • 你为什么还要通过 fetch 呢?如果我正确阅读了您的代码,它的作用是首先在某个地址获取资源,然后生成一个指向该获取数据的 blob URI。鉴于此过程仅限于同源策略,为什么不直接将您的锚点指向要获取的资源?这里根本不需要 blob:// URI。 (另外,如果你真的很好奇,当你执行fetch(url).then(r => r.blob()) 时,必须将整个数据作为 ReadableStream 获取,并存储在 ArrayBuffer 中。只有当整个请求完成时,ArrayBuffer 才会被复制到 Blob 中。 )
  • @Kaiido 首先,此方法确保浏览器下载 txt、pdf、mp4、HTML 等文件类型而不是打开它们。其次,这确保我们可以循环(或在本例中设置间隔)一组 downloadLinks 并创建 blob URL 的锚元素。事实证明,http[s]://* 类型的 URL 无法做到这一点,而 blob://* 类型的 URL 可以做到这一点。我在stackoverflow.com/questions/66666415/… 中讨论过这种行为
  • @Kaiido 我还注意到使用 fetch API 并不是做我想做的事情的最佳方式。正如您所指出的, fetch 首先将数据作为 ReadableStream 加载到内存中,然后才将其转换为 Blob 对象。因此,我现在正在使用 xhr 客户端。设置 xhr.responsetype = 'blob' 可确保数据以 blob 形式获取/加载。

标签: javascript memory download fetch blob


【解决方案1】:

简短的回答是肯定的!

  1. Fetch 实际上改变了这种行为,因为它首先将数据作为ReadableStream 加载到内存中。因此,请改用以下代码。

  2. 此方法可以下载的最大文件大小取决于磁盘大小、操作系统和浏览器。没有适用于所有系统的确切数字。这个问题已经详细解答here

  3. “没有明显的硬限制。我能够创建比“800 MiB”FileSaver.js 声称的大得多的 Blob。它不使用磁盘空间来支持更大的 Blob,所以它都在内存中,可能与操作系统将内存分页到磁盘。这意味着可能会出现大于内存的 blob,尽管性能可能很差。"

    点击 2 号链接和here 了解更多详情。

正如@kaiido 在评论中提到的以及我通过运行一些测试也发现的东西,如果您期望一个大文件以及如何利用 Blob 架构(如果可能的话,直接在磁盘中加载文件)以上代码可以修改如下。

const download = downloadLinks => {

    const _download = url => {

        const xhr = new XMLHttpClient()
        xhr.responseType = 'blob'
        xhr.open('GET', url)
        xhr.onload = () => {
            const fileName = url.substr(url.lastIndexOf('/'))
            const blobURL = window.URL.createObjectURL(xhr.response)
            const a = document.createElement('a')  
            a.href = blobURL
            a.setAttribute('download', fileName)  
            document.body.appendChild(a)  
            a.click()
            a.remove()  
            window.URL.revokeObjectURL(blobURL)
        }
        xhr.send(null)

    }

    const downloadInterval = () => {

        if (downloadLinks.length == 0) return

        const url = downloadLinks.pop()
        
        _download(url)
        
        if (downloadLinks.length !== 0) setTimeout(downloadInterval, 500)

    }

    setTimeout(downloadInterval, 0)
}

这里的区别在于xhr.responseType = 'blob' 这一行。虽然可以在原始问题中看到我们的请求对象有一个 responseType 选项,但它不起作用,因为 fetch API 一开始就没有该选项。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-03
    • 1970-01-01
    • 2018-03-20
    • 2019-02-12
    • 2022-01-12
    • 2020-01-07
    • 2023-03-17
    • 2017-10-25
    相关资源
    最近更新 更多