js Fetch返回數據res.json()報錯問題
前言
一直以來在簡單的場景中經常使用fetch代替第三方請求庫, fetch是JavaScript的原生函數, 簡單、高效、快速、穩定、可訂製等等諸多優點。一直也是用著很是舒服,直到有一天它竟然報錯了。
什麼是Fetch?
官方: Fetch API 提供了一個 JavaScript 介面,用於訪問和操縱 HTTP 管道的一些具體部分,例如請求和響應。它還提供了一個全局 fetch() 方法,該方法提供了一種簡單,合理的方式來跨網路非同步獲取資源。
還原現場
因為業務需求簡單,這裡只封裝了get和post方法, 並且後端數據都是已默認的json格式返回
const http = {
apiBaseUrl: config.apiBaseUrl,
get: function (url) {
return new Promise((resolve, reject) => {
fetch(this.apiBaseUrl + url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
}).then(res => res.json()).then(res => {
resolve(res);
}).catch(e => {
console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
reject(e);
});
})
},
post: function (url, body) {
return new Promise((resolve, reject) => {
fetch(this.apiBaseUrl + url, {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}, body: JSON.stringify(body)
}).then(res => res.json()).then(res => {
resolve(res);
}).catch(e => {
console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
reject(e);
})
})
}
}
這樣的封裝貌似也沒什麼問題 😃。
後端沒有做統一的返回數據格式約定, 直接使用.net中的ActionResult結構返回, .net封裝的應該都是很好用的 😃
/// <summary>
/// 模擬處理數據
/// </summary>
/// <returns></returns>
[HttpPost("handle-data")]
public ActionResult HandleData(string mockData)
{
// 業務邏輯
// ......
// 處理完成,直接告訴瀏覽器我Ok了, 狀態200
return Ok();
}
此時前端在處理完成後, 控制檯面板中 Network 中也顯示post成功返回200了, 但是在 Console 中卻有一條紅色報錯資訊
請求失敗了,詳細資訊:{}

看到報錯位置竟然是post中的catch, 可明明在Network中看到返回200了啊, 稍作鎮靜之後就意識到應該就是返回時數據處理報錯了, 在resolve(res)上面列印也沒走這個邏輯, 那就是 上一層 .then(res=> res.json()有問題。
// 將.then(res=> res.json()) 替換成下面
.then(res => {
console.log(res.json());
return res.json();
})
這樣寫同樣報錯

可以看到時解析json報錯了, 嗯, 因為我們就是沒有返回任何數據, 解析自然會報錯!
既然沒有值Json解析報錯, 那解決辦法自然就得加一層判斷了(也可以讓後端必須返回一個Json, 簡單粗暴, 哈哈! ) , 思路是先讀取值然後判斷是否為空.
但是列印res時
// 將.then(res=> res.json()) 替換成下面
.then(res => {
console.log(res);
let arrayBuffer = res.json();
let json = res.json();
return res.json();
})
body: ReadableStream
locked: true
[[Prototype]]: ReadableStream
bodyUsed: true
headers: Headers
[[Prototype]]: Headers
ok: true
redirected: false
status: 200
statusText: "OK"
type: "cors"
url: "'//localhost:5069/api/handle-data"
[[Prototype]]: Response
而且還有一條報錯資訊
Uncaught (in promise) TypeError: Failed to execute 'json' on 'Response': body stream already read
body stream already read說明流只能讀取一次,
body是一個ReadableStream數據流,必須先讀取流才能看到數據, 那就看一下是否還能轉換成其他格式的數據.
查找MDN //developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch#body
Body 類定義了以下方法(這些方法都被
Request和Response所實現)以獲取 body 內容。這些方法都會返回一個被解析後的 Promise 對象和數據。
可知有5種數據格式,因為json和text可使用js原生方法JSON.parse/JSON.stringify相互轉換, 那就直接選用.text()轉成字元串判斷即可.
// 將.then(res=> res.json()) 替換成下面
.then(res => {
let data = res.text();//轉成字元串判斷
return data.then(r => {
if (r.length === 0) return null;
else return JSON.parse(r);
})
})
驗證結果正確, 一切又回到了正常。
簡單封裝fetch 獲取json或空數據
let checkStatus = res => {
if (res.status >= 200 && res.status < 300) return res;
else {
let err = new Error(res.statusText);
err.response = res;
throw err;
}
}
let parseJson = res => {
let data = res.text();
return data.then(r => {
if (r.length === 0) return null;
else return JSON.parse(r);
})
}
const http = {
apiBaseUrl: config.apiBaseUrl,
get: function (url) {
return new Promise((resolve, reject) => {
fetch(this.apiBaseUrl + url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
}).then(checkStatus).then(parseJson).then(res => {
resolve(res);
}).catch(e => {
console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
reject(e);
});
})
},
post: function (url, body) {
return new Promise((resolve, reject) => {
fetch(this.apiBaseUrl + url, {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}, body: JSON.stringify(body)
}).then(checkStatus).then(parseJson).then(res => {
resolve(res);
}).catch(e => {
console.error("請求失敗了,詳細資訊:" + JSON.stringify(e));
reject(e);
})
})
}
}
嗯 , 前端能處理的問題就不麻煩後端了 😃
總結
fetch返回的是數據流, 最終是什麼格式數據需要我們自己判斷使用, arrayBuffer/blob/formData/json/text 這些格式都有其使用場景, 前端er不要將思維只限定在了json哦。
作者:wwmin
出處://www.cnblogs.com/cnwwm
聯繫:[email protected] 微信:w_wmin
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。


