相关文章推荐
强悍的创口贴  ·  使用 Java 管理 Blob ...·  2 月前    · 
睿智的牛肉面  ·  python ...·  2 月前    · 
骑白马的香菜  ·  Java ...·  2 月前    · 
豁达的跑步鞋  ·  lstm单步预测python ...·  1 年前    · 

谈一谈 Fetch API 中的 “res.blob()”

这篇文章主要回答了三个问题:

  1. 如何处理 Fecth API 从后端获取的字节数据?
  2. URL.createObjectURL() 做了什么?
  3. “res.blob()” 做了什么?




当我们使用 Fetch API 获取到后端返回的字节流一般都会通过 “res.blob()” 转换成 blob 对象再进一步处理( Fetch API ),那么问题来了 --- ”res.blob()” 又做了什么?

至于为什么想谈这个,是因为之前小伙伴问我:怎么下载后端返回的 zip 文件?所以就想写篇文章来谈一谈这里涉及到的知识点,其中最关键的就是 “res.blob()”。


处理 Fecth API 从后端获取的字节数据

先回答下小伙伴提的问题:怎么下载后端返回的 zip 文件?

这个问题还是比较好解决的,从后端返回的字节数据,都可以通过调用 response 对象的 blob() 方法来将它转换成返回 blob 的 Promise。

那 blob 对象又是什么?我们可以把它当作原始数据对象,它保存着从后端返回的原始数据以及相关信息(比如字节数以及类型)。我们可以通过它获得 Base64 URL 或者 blob URL。然后通过 a 标签的 download 属性来设置文件名,将获得的 URL 赋给 a 标签的 href 属性就大功告成了,当然别忘了调用 click() 开始下载。

示例代码如下:

fetch('example.zip')
.then(res => res.blob())
.then(blob => {
    // 通过 blob 对象获取对应的 url
    const url = URL.createObjectURL(blob)
    let a = document.createElement('a')
    a.download = 'example.zip'
    a.href = url
    document.body.appendChild(a)
    a.click()
    a.remove() // document.body.removeChild(a)
.catch(err => {
    console.error(err)


URL.createObjectURL() 做了什么?

问题看起来是解决了,但是有人可能会问:URL.createObjectURL() 方法到底做了什么,为什么我们能通过它来获取到文件下载的地址?

这里简单说一下,每次调用 URL.createObjectURL() 方法都会生成一个地址,这个地址代表着根据 blob 对象生成的资源入口,而这个资源入口存放于浏览器维护的一个 blob URL store 中。生成的 URL 由四部分组成:

  1. ‘blob: ’
  2. origin
  3. ‘/’
  4. UUID

生成的 URL 大概都长这样:

blob:https://www.example.com/7914f88e-cac5-4af5-96a1-287584d518b5

这里要注意两个地方:

  1. 即使是同一个 blob 对象,每次调用都会生成不同的 URL。
  2. URL 不再用的时候,需要调用 URL.revokeObjectURL() 来撤销这个入口。(如果不撤销,URL 会一直被保留,直到当前 document 对象 unload)


“res.blob()”

剩下的,就是这篇文章的主角了,点题的一节 — “res.blob()”。

对于 res.blob(),我最想知道的是它到底做了什么?这一节就来谈一谈。

res.blob() 到底做了什么?

每次调用 res.blob() 方法都会执行 “consume body” 动作,“consume body” 的流程大概是这样的:

  1. 获取字节流的读取器
  2. 通过读取器读取所有的数据
  3. 把数据包装成 blob 对象并返回

既然 res.blob() 可以处理字节流,那么我们能不能自己去处理后端返回的字节流,毕竟我们有时候也会遇到这种情况吧(没有的事,吃饱了撑着)。

接下来就直接放代码,模拟 res.blob(),这里需要注意的是兼容性,可以参考: Can I use... Support tables for HTML5, CSS3, etc

fetch('example.zip')
.then((res) => {
    // 获取读取器
    const reader = res.body.getReader()
    const type = res.headers.get('Content-Type')
    const data = []
    return new Promise((resolve) => {
        // 读取所有数据
        function push() {
            reader.read().then(({done, value}) => {
                data.push(value)
                if (done) {
                    // 包装成 blob 对象并返回
                    resolve(new Blob(data, { type }))
                } else {
                    push()
        push()
.then(blob => {
    const url = URL.createObjectURL(blob)
    let a = document.createElement('a')
    a.download = 'example.zip'