fetch ios低版本兼容 cannot clone a disturbed response
报错信息
ios 11以下 cannot clone a disturbed response
问题发生场景
- 使用了一个或者多个三方库
- 三方库或者自己的业务代码重写了fetch
- ios11以下
核心原因 ios低版本兼容问题,fetch的原始响应clone一次解析后,不能再次clone(浏览器报错信息:cannot clone a disturbed response)
我们使用fetch的响应的时候,如果直接通过方法解析2次,第二次就会报错 body stream already read
1 2 3 4 | fetch( "/" ).then(res=>{ res.text().then((r)=>{console.log(r)}) res.text().then((r)=>{console.log(r)}) }); |
所以一般会使用clone,如下的写法。这样的写法有兼容问题,ios11以下会报错: cannot clone a disturbed response
1 2 3 4 | fetch( "/" ).then(res=>{ res.clone().text().then((r)=>{console.log(r)}) res.clone().text().then((r)=>{console.log(r)}) }); |
这个时候有同学会问了,谁会这样写啊,一般解析一次就够了,干嘛解析两次。如果使用了三方库就会出现这种问题,一般三方库会重写fetch的。三方库可能是请求库(umi-request),也可能是调试库(eduda、vconsole),等等。三方库,会重写fetch,为了拦截API写点自己需要的代码,大概是下面这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 三方库重写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… )
从目前的代码看起来,这个解决方案只是解决了它内部使用的问题,而且它返回的数据并不是fetch的原始响应,而是它解析后的接口结果。
现在假如我们在umi/request之后,再实例化使用vconsle,或者eruda,这两个库会重写fetch。两个库同时存在的时候,res.clone 就会触发开始说的ios低版本问题。
vconsole
下面这段是vconsole的fetch代码
eruda
几乎大多的库都如上面,fetch返回的原始响应在库内部被clone过后,原始响应再流转下去。流转下去以后其他的三方库或者业务代码,执行clone便会触发ios11以下的兼容问题。就像是执行了下面的代码一样。
1 2 3 4 5 6 7 8 | 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以下也不会有问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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... |
理一下关系
会出兼容性问题的写法图示
解决方案图示
我们多clone一级,就能解决这个问题,这和clone本身的意义实际有出处。ios11以下的这个兼容问题,应该是以前的bug,这个bug在ios11以后才修复,总之现在这样就能解决问题。
解决方案已经明确了,三方库推荐如下方式修改clone方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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: 水印就不去了,先在掘金编辑的,拷贝过来多平台发布,本文章为原创文。


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)