fetch ios低版本兼容cannot clone a disturbed response

2021-09-09

报错信息

ios 11以下 cannot clone a disturbed response

github.com/github/fetc…

问题发生场景

  • 使用了一个或者多个三方库
  • 三方库或者自己的业务代码重写了fetch
  • ios11以下

核心原因 ios低版本兼容问题,fetch的原始响应clone一次解析后,不能再次clone(浏览器报错信息:cannot clone a disturbed response)

我们使用fetch的响应的时候,如果直接通过方法解析2次,第二次就会报错 body stream already read

fetch("/").then(res=>{
    res.text().then((r)=>{console.log(r)})
    res.text().then((r)=>{console.log(r)})
});

 

所以一般会使用clone,如下的写法。这样的写法有兼容问题,ios11以下会报错: cannot clone a disturbed response

fetch("/").then(res=>{
    res.clone().text().then((r)=>{console.log(r)})
    res.clone().text().then((r)=>{console.log(r)})
}); 

 

这个时候有同学会问了,谁会这样写啊,一般解析一次就够了,干嘛解析两次。如果使用了三方库就会出现这种问题,一般三方库会重写fetch的。三方库可能是请求库(axois、umi-request),也可能是调试库(eduda、vconsole),等等。三方库,会重写fetch,为了拦截API写点自己需要的代码,大概是下面这样的:

// 三方库重写fetch代码
const originFetch = fetch;
fetch = function(){
    // do some
    return originFetch
          .apply(this, arguments)
          .then((res) => {
              // do some
              res.clone().text().then((data) => {
                  // do some
              })
              return res
          })
}
// 业务代码
fetch('/').then(res=>{
    res.clone().text()
})

 

如上代码,返回的 res 已经被三方库 clone 过了,如果再次 clone 便会出现ios11以下的兼容报错。所以我们的业务代码会直接报错,拿不到任何响应。

三方库分析

umi/request

umi/request,发现了这个问题,并且做了代码的处理. ( github.com/umijs/umi-r… )

image.png

 

github.com/umijs/umi-r…

 

image.png

 

从目前的代码看起来,这个解决方案只是解决了它内部使用的问题,而且它返回的数据并不是fetch的原始响应,而是它解析后的接口结果。

现在假如我们在umi/request之后,再实例化使用vconsle,或者eruda,这两个库会重写fetch。两个库同时存在的时候,res.clone 就会触发开始说的ios低版本问题。

 

vconsole

下面这段是vconsole的fetch代码

image.png

 

eruda

github.com/liriliri/ch…

image.png

 

github.com/liriliri/ch…

image.png

几乎大多的库都如上面,fetch返回的原始响应在库内部被clone过后,原始响应再流转下去。流转下去以后其他的三方库或者业务代码,执行clone便会触发ios11以下的兼容问题。就像是执行了下面的代码一样。

fetch("/").then(res=>{
    // 第一次clone
    res.clone().text().then((r)=>{console.log(r)})
    return res
}).then(res=>{
    // 第二次clone
    res.clone().text().then((r)=>{console.log(r)})
});

 

解决方案

如果业务代码使用原生fetch只会解析一次fetch响应,可以忽略因为不会触发两次clone。 作为三方库的开发者,应该知道有这样的兼容问题,下面的写法ios11以下也不会有问题。

fetch("/").then(res=>{
    // 第一次clone
    const C1 = res.clone();
    const C2 = res.clone();
    C1.clone().text().then((r)=>{console.log(r)})
    C2.clone().text().then((r)=>{console.log(r)})
})
// else
fetch("/").then(res=>{
    // 第一次clone
    const C1 = res.clone();
    C1.clone().text().then((r)=>{console.log(r)})
    C1.clone().text().then((r)=>{console.log(r)})
})
// else...

 

理一下关系

会出兼容性问题的写法图示

image.png

 

解决方案图示

image.png

 

我们多clone一级,就能解决这个问题,这和clone本身的意义实际有出处。ios11以下的这个兼容问题,应该是以前的bug,这个bug在ios11以后才修复,总之现在这样就能解决问题。

解决方案已经明确了,三方库推荐如下方式修改clone方法。

const originFetch = fetch;
fetch = function(){
    // do some
    return originFetch
          .apply(this, arguments)
          .then((res) => {
              const copyClone = res.clone();
              // do some
              copyClone.clone().text().then((data) => {
                  // do some
              })
              return copyClone.clone()
          })
}

 

如果同时引入多个三方库,其中一个已经按照下面写法解决了兼容性问题,一个还没有解决,可以让解决了兼容的库先执行,也能保证运行正常。

 

同时还发现,ios11以下,fetch finally方法undefined,不能使用finally方法

 

ps: 水印就不去了,先在掘金编辑的,拷贝过来多平台发布,本文章为原创文。