報錯資訊

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… )

github.com/umijs/umi-r…

從目前的程式碼看起來,這個解決方案只是解決了它內部使用的問題,而且它返回的資料並不是fetch的原始響應,而是它解析後的介面結果。

現在假如我們在umi/request之後,再例項化使用vconsle,或者eruda,這兩個庫會重寫fetch。兩個庫同時存在的時候,res.clone 就會觸發開始說的ios低版本問題。

vconsole

下面這段是vconsole的fetch程式碼

eruda

github.com/liriliri/ch…

github.com/liriliri/ch…

幾乎大多的庫都如上面,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...

理一下關係

會出相容性問題的寫法圖示

解決方案圖示

我們多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: 水印就不去了,先在掘金編輯的,拷貝過來多平臺釋出,本文章為原創文。