前言
一直以来在简单的场景中经常使用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
出处:https://www.cnblogs.com/cnwwm
联系:wwei.min@163.com 微信:w_wmin
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。如有问题或建议,请多多赐教,非常感谢。