報錯信息
ios 11以下 cannot clone a disturbed response
問題發生場景
- 使用了一個或者多個三方庫
- 三方庫或者自己的業務代碼重寫了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… )
從目前的代碼看起來,這個解決方案只是解決了它內部使用的問題,而且它返回的數據並不是fetch的原始響應,而是它解析后的接口結果。
現在假如我們在umi/request之后,再實例化使用vconsle,或者eruda,這兩個庫會重寫fetch。兩個庫同時存在的時候,res.clone 就會觸發開始說的ios低版本問題。
vconsole
下面這段是vconsole的fetch代碼
eruda
幾乎大多的庫都如上面,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: 水印就不去了,先在掘金編輯的,拷貝過來多平台發布,本文章為原創文。