Fetch方法封装、业务实践
说Fetch之前啊,我们不得不说一说Ajax了,以前使用最多的自然是jQuery封装的Ajax方法了,强大而且好用。
有人说了,jQuery的Ajax都已经封装得那么好了,你还整Fetch干什么,这不是多此一举嘛。实际上,在大家写业务需求的时候能够感觉到,越是复杂的业务逻辑,Ajax用起来就会显得有点费劲,因为它的异步回调机制,很容易让我们陷入嵌套循环中,业务代码就会变得非常臃肿而难以理解。而Fetch请求则可以写成同步的模式,一步一步执行,阅读起来就非常舒服。
有人又说了,Ajax也能写成同步的啊,用Promise封装一个就好了。你说得对,可那不是有点多余吗,有现成的你不用,还得搭上一个jQuery,不划算。总之,我们使用Fetch来进行请求,轻量而且方便。
题外话:你把接口写完了,是不是得了解一下接口请求是不是成功啦,请求成功率有多高呢;接口耗时怎么样啊,超时的接口又有多少;那么如何监控接口请求的性能问题,可以看下我写的前端监控系统:www.webfunny.cn 或者github搜索:webfunny_monitor 欢迎了解。
题外话说完,下边开始说干货了。
Fetch本身其实跟Ajax一样都是对XMLHttpRequest的封装,而且封装的很不错了,为什么还要封装呢,我们且看下fetch请求的写法。
// fetch原本方法的写法
fetch(url, {
method: 'GET';
}).then(res => {
res.json();
}).then(data => {
console.log(data);
}).catch(error => {
console.log(error.msg);
})
请看,上边的代码算是最简单的fetch请求方法了,写出来是这个样子的,虽然简单,但是调用链也忒长了点儿啊,我们在看下相对较复杂一点的fetch请求写法。
// fetch原本方法的写法
fetch(url, {
method: "POST",
headers: {
"Accept": "*/*",
"Content-Type": "application/json;charset=utf-8",
"access-token": "token"
}
}).then(res => {
res.json()
}).then(data => {
console.log(data)
}).catch(error => {
console.log(error.msg)
})
}
上边带一坨headers, 下边带一坨catch,十分恶心。像这个简单的调用方法,还没有处理超时、报错、404等,都够咱们喝一壶了,所以业务实践,Fetch方法必须要封装。
一、区分请求类型
一般请求的方式有多重类型如:PUT,POST,GET,DELETE;今天我们就以最常见的post、get为例进行封装。不管是用什么方法请求,请求所需要的参数和逻辑处理都是大致相同的,所以我们要做的就是把这些共用的部分提取出来。
我们先来解释一下它的参数,url:请求路径;param:请求参数,get、post分别用不同的方式传入;httpCustomerOpertion(自定义参数),isHandleResult:是否需要处理错误结果,比如toast错误信息,上报错误信息等等;isShowLoading:是否显示loading,每个接口请求都需要时间,是否显示loading效果;customerHead:是否使用自定义的请求头;timeout: 自定义接口超时的时间
以上参数虽然繁琐,但都是很有必要的,代码如下:
/**
* get 请求
* @param url
* @param params
* @param isHandleError
* @param httpCustomerOpertion 使用者传递过来的参数, 用于以后的扩展用户自定义的行为
* {
* isHandleResult: boolen //是否需要处理错误结果 true 需要/false 不需要
* isShowLoading: boolen //是否需要显示loading动画
* customHead: object // 自定义的请求头
* timeout: int //自定义接口超时的时间
* }
* @returns {Promise}
*/
get(url, params = {}, httpCustomerOpertion = { isHandleResult: true, isShowLoading: true }) {
if (!httpCustomerOpertion.hasOwnProperty("isHandleResult")) {
httpCustomerOpertion.isHandleResult = true
}
if (!httpCustomerOpertion.hasOwnProperty("isShowLoading")) {
httpCustomerOpertion.isShowLoading = true
}
const method = "GET"
const fetchUrl = url + CommonTool.qs(params) // 将参数转化到url上
const fetchParams = Object.assign({}, { method }, this.getHeaders())
return HttpUtil.handleFetchData(fetchUrl, fetchParams, httpCustomerOpertion)
} /**
* post 请求
* @param url
* @param params
* @param isHandleError
* @param httpCustomerOpertion 使用者传递过来的参数, 用于以后的扩展用户自定义的行为
* @returns {Promise}
*/
post(url, params = {}, httpCustomerOpertion = { isHandleResult: true, isShowLoading: true }) {
if (!httpCustomerOpertion.hasOwnProperty("isHandleResult")) {
httpCustomerOpertion.isHandleResult = true
}
if (!httpCustomerOpertion.hasOwnProperty("isShowLoading")) {
httpCustomerOpertion.isShowLoading = true
}
const method = "POST"
const body = JSON.stringify(params) // 将参数转化成JSON字符串
const fetchParams = Object.assign({}, { method, body }, this.getHeaders())
return HttpUtil.handleFetchData(url, fetchParams, httpCustomerOpertion)
}
二、fetch请求业务逻辑封装
请求方式区分开了,接下来我们对fetch的核心内容进行封装。
1. 判断自定义请求信息,loading,header。
2. 对fetch请求再进行一次封装。
3. 放弃迟到的响应,轮询时,有可能发生这种情况,后边发出的接口请求,提前获得了返回结果,就需要放弃前一次的请求结果。
4. 统一处理返回结果,后台业务返回结果,无论结果如何,说明接口正常。业务逻辑再区分正常和异常,比如token超时,业务异常等等。
5. 接口状态判断,第4点说了,返回结果,无论结果如何,接口都是正常的;如果接口状态是非200类型的,说明接口本身出错了,如404,500等,需要单独捕获和处理。
6. 接口超时处理,由于fetch本身并不支持超时判断,所以我们需要利用promise.race方法来判断超时问题。
核心代码如下:
/**
* 发送fetch请求
* @param fetchUrl
* @param fetchParams
* @returns {Promise}
*/
static handleFetchData(fetchUrl, fetchParams, httpCustomerOpertion) {
// 1. 处理的第一步
const { isShowLoading } = httpCustomerOpertion
if (isShowLoading) {
HttpUtil.showLoading()
}
httpCustomerOpertion.isFetched = false
httpCustomerOpertion.isAbort = false
// 处理自定义的请求头
if (httpCustomerOpertion.hasOwnProperty("customHead")) {
const { customHead } = httpCustomerOpertion
fetchParams.headers = Object.assign({}, fetchParams.headers, customHead)
}
// 2. 对fetch请求再进行一次Promise的封装
const fetchPromise = new Promise((resolve, reject) => {
fetch(fetchUrl, fetchParams).then(
response => {
// 3. 放弃迟到的响应
if (httpCustomerOpertion.isAbort) {
// 3. 请求超时后,放弃迟到的响应
return
}
if (isShowLoading) {
HttpUtil.hideLoading()
}
httpCustomerOpertion.isFetched = true
response.json().then(jsonBody => {
if (response.ok) {
// 4. 统一处理返回结果
if (jsonBody.status === 5) {
// token失效,重新登录
CommonTool.turnToLogin()
} else if (jsonBody.status) {
// 业务逻辑报错, 不属于接口报错的范畴
reject(HttpUtil.handleFailedResult(jsonBody, httpCustomerOpertion))
} else {
resolve(HttpUtil.handleResult(jsonBody, httpCustomerOpertion))
}
} else {
// 5. 接口状态判断
// http status header <200 || >299
let msg = "当前服务繁忙,请稍后再试"
if (response.status === 404) {
msg = "您访问的内容走丢了…"
}
Toast.info(msg, 2)
reject(HttpUtil.handleFailedResult({ fetchStatus: "error", netStatus: response.status, error: msg }, httpCustomerOpertion))
}
}).catch(e => {
const errMsg = e.name + " " + e.message
reject(HttpUtil.handleFailedResult({ fetchStatus: "error", error: errMsg, netStatus: response.status }, httpCustomerOpertion))
})
}
).catch(e => {
const errMsg = e.name + " " + e.message
// console.error('ERR:', fetchUrl, errMsg)
if (httpCustomerOpertion.isAbort) {
// 请求超时后,放弃迟到的响应
return
}
if (isShowLoading) {
HttpUtil.hideLoading()
}
httpCustomerOpertion.isFetched = true
httpCustomerOpertion.isHandleResult && Toast.info("网络开小差了,稍后再试吧", 2)
reject(HttpUtil.handleFailedResult({ fetchStatus: "error", error: errMsg }, httpCustomerOpertion))
})
})
return Promise.race([fetchPromise, HttpUtil.fetchTimeout(httpCustomerOpertion)])
}
三、通用逻辑单独封装
代码里注释也非常详尽了,我就不一一赘述了
/**
* 统一处理后台返回的结果, 包括业务逻辑报错的结果
* @param result
* ps: 通过 this.isHandleError 来判断是否需要有fetch方法来统一处理错误信息
*/
static handleResult(result, httpCustomerOpertion) {
if (result.status && httpCustomerOpertion.isHandleResult === true) {
const errMsg = result.msg || result.message || "服务器开小差了,稍后再试吧"
const errStr = `${errMsg}(${result.status})`
HttpUtil.hideLoading()
Toast.info(errStr, 2)
}
return result
}
/**
* 统一处fetch的异常, 不包括业务逻辑报错
* @param result
* ps: 通过 this.isHandleError 来判断是否需要有fetch方法来统一处理错误信息
*/
static handleFailedResult(result, httpCustomerOpertion) {
if (result.status && httpCustomerOpertion.isHandleResult === true) {
const errMsg = result.msg || result.message || "服务器开小差了,稍后再试吧"
const errStr = `${errMsg}(${result.status})`
HttpUtil.hideLoading()
Toast.info(errStr, 2)
}
const errorMsg = "Uncaught PromiseError: " + (result.netStatus || "") + " " + (result.error || result.msg || result.message || "")
return errorMsg
}
/**
* 控制Fetch请求是否超时
* @returns {Promise}
*/
static fetchTimeout(httpCustomerOpertion) {
const { isShowLoading } = httpCustomerOpertion
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!httpCustomerOpertion.isFetched) {
// 还未收到响应,则开始超时逻辑,并标记fetch需要放弃
httpCustomerOpertion.isAbort = true
// console.error('ERR: 请求超时')
if (isShowLoading) {
HttpUtil.hideLoading()
}
Toast.info("网络开小差了,稍后再试吧", 2)
reject({ fetchStatus: "timeout" })
}
}, httpCustomerOpertion.timeout || timeout)
})
}
到此,fetch请求的封装就算完成了,如此封装的fetch请求方法可以兼容很多业务场景了。
四、与后端接口的约定
当然,接口请求从来都不仅仅是前端一个人的事情,还需要跟后台的小伙伴进行约定。 比如:返回结果包含status, status = 0,说明业务逻辑正常,我们只管取结果即可; status!=0,说明业务逻辑异常,后端将异常信息放到msg字段里,前端将其Toast出来,展现给用户。
五、封装后的调用方式
export const fetchAction = (param, handleResult, handleFailResult) => {
return HttpUtil.post(HttpApi.url, param).then( response => {
handleResult(response.data)
}).catch((e) => {
handleFailResult()
console.error(e)
})
}
// 调用的结果就是这样的
await fetchAction1
await fetchAction2
await fetchAction3
结语
Fetch方法的封装说到底就为了满足更多的业务场景,而且现实的业务场景中,我在上文中基本都已经提到了。也许你不能完全理解代码中所写的东西,但是你只要能理解为什么要做这些事情,你就知道该怎么去封装这些请求方法了。
Fetch方法封装、业务实践的更多相关文章
- ES6 fetch方法封装
// 请求路径 let url = 'http://jsonplaceholder.typicode.com/users' // 传输数据参数 const dataName = { name: &qu ...
- STORM在线业务实践-集群空闲CPU飙高问题排查
源:http://daiwa.ninja/index.php/2015/07/18/storm-cpu-overload/ 2015-07-18AUTHORDAIWA STORM在线业务实践-集群空闲 ...
- C#方法封装与重构
C#作为一个完全面向对象的语言,有个特性很重要但是往往会不重视,而不重视的结果就会造成代码杂乱难以解读.维护.这个特性就是封装. 这里不是大谈C#的封装,我只讲一个,关于方法封装的一些问题. ...
- AngularJS之使用控制器封装业务逻辑
AngularJS之使用控制器封装业务逻辑 控制器的作用 我们知道,在AngularJS中,实现数据绑定的核心是scope对象.那么控制器又有什么用呢? 简单地说,没有控制器/controller,我 ...
- React Native之Fetch简单封装、获取网络状态
1.Fetch的使用 fetch的使用非常简单,只需传入请求的url fetch('https://facebook.github.io/react-native/movies.json'); 当然是 ...
- 【重点突破】—— fetch()方法介绍
前言:ant-design-pro的技术组成主要是react+redux+dva+antd+fetch+roadhog,dva在源码包index.js里面导出了fetch,但是如果不想使用fetch库 ...
- react-native fetch 请求封装
1.fetch 函数封装 fetch.js /** * 请求头 * @type {{Accept: string, Content-Type: string}} */ const header = { ...
- 前端js重组树形结构数据方法封装
不知道大家平时工作中,有没有遇到这样一种情况:后端接口返回的数据,全都是一维的数组,都是平铺直叙式的数据,业务需求却要你实现树形结构的功能.那么,针对这种情况该怎么办呢?是跟后台好好沟通一下呢,还是沟 ...
- js浮点数保留位数方法封装
大家在平时业务中应该经常跟小数打交道吧,有没有被小数点的保留位数问题搞得头疼啊.比如,保留一位小数,保留俩位小数,保留三位小数,向上取整.四舍五入等等. 而我最近在项目中正好遇到类似的问题:有的地方要 ...
随机推荐
- Linux 用户组管理命令
groupadd 组名,可以添加用户组 groupmod -n 新组名 老组名,可以修改组名 groupdel 组名,可以删除组(组中不能有初始用户存在,附加用户无所谓) gpasswd -a 用户名 ...
- mac下使用VMVARE安装win10虚拟机的一些坑
最近Mac上安装windows踩到了几个坑: 坑一:启动虚拟机后,提示找不到CD-ROM中找不到对应的ISO文件 硬盘格式请选择 在虚拟机->设置中选择启动磁盘为CD_ROM,然后重新启动. 坑 ...
- CUDA优化
cuda程序优化 一:程序优化概述 1:精度 在关键步骤使用双精度,其他步骤使用单精度,以获得指令吞吐量和精度的平衡. 2:延迟 先缓冲一定量数据,在交给GPU计算.可以获得较高的数据吞吐量. 3:计 ...
- CentOS7搭建Pacemaker高可用集群(1)
Pacemaker是Red Hat High Availability Add-on的一部分.在RHEL上进行试用的最简单方法是从Scientific Linux 或CentOS存储库中进行安装 环境 ...
- hadoop启动后,9000端口无法连接,netstat -tpnl中找不到该端口
已解决: 需要重新格式化hdfs. 1.停止hdfs: 2.删除hdfs的相关文件目录(hdfs-site.xml中配置的存放文件的目录). 3.启动journalnode:sbin/hadoop-d ...
- 运行ABP(asp.net core 3.X+Vue)提示'OFFSET' 附近有语法错误。 在 FETCH 语句中选项 NEXT 的用法无效。
创建ASP.NET Boilerplate,还原数据库和启动客户端 这里就略过,具体参考 ABP框架(asp.net core 2.X+Vue)模板项目学习之路(一) ASP.NET Boilerpl ...
- MySQL数据库字符集和排序规则的四个级别
MySQL数据库字符集和排序规则有四个级别的默认设置:服务器,数据库,表和列. 最初,服务器字符集和排序规则取决于启动mysqld时使用的选项.可以使用 --character-set-server该 ...
- 蒲公英 · JELLY技术周刊 Vol.10 为什么你还不使用 TypeScript
登高远眺 天高地迥,觉宇宙之无穷 基础技术 直播延迟?为什么流媒体直播的延迟那么高 随着通信技术的发展,视频点播.直播业务也逐渐兴起.在这些业务形态中,流媒体技术扮演着重要的角色.但在实际使用中,即便 ...
- Python Opencv-contrib Camshift&kalman卡尔曼滤波&CSRT算法 目标跟踪实现
本次课题实现目标跟踪一共用到了三个算法,分别是Camshift.Kalman.CSRT,基于Python语言的Tkinter模块实现GUI与接口设计,项目一共包含三个文件: main.py: # co ...
- HashMap(一)之源码分析基本功
1. 位运算 参考 java中位运算^,&,<<,>>,<<<,>>>总结 2. 关键字 transient 理解一下这个关键字,顺 ...