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


報錯信息

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的。三方庫可能是請求庫(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: 水印就不去了,先在掘金編輯的,拷貝過來多平台發布,本文章為原創文。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM