前言
一直以來在簡單的場景中經常使用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: "'http://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 https://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
聯系:wwei.min@163.com
公眾號: DotNet技術說
歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。