这是我参与更文挑战的第12天,活动详情查看: 更文挑战
前言
在上次写完记在实战项目中对于请求模块的无敌封装的文章后,我把一个严重影响用户体验的问题提上了日程。
可以看完上述文章后和我一起改进请求模块。
如何取消Pomrise?
场景
左右页面布局,点击左侧任务列表中的元素,获取右侧任务详情。
对于这种场景中,正常测试下来其实都没有问题,但是频繁地切换点击左侧任务列表,就会有个很严重的体验问题。
由于左侧点击后,运行getTaskDetail
函数,逻辑如下。
- 打开右侧详情页面的
loading
。 - 获取筛选的条件。
- 根据点击的任务获取id或其他值请求接口。
- 获取接口返回的数据后赋值至页面变量。
- 关闭右侧详情页面的
loading
。
async getTaskDetail() {
this.rightLoading = true
const httpObj: HttpObj = await this.checkHttpObj()
const {
data: { checklists, students },
meta: { school_alias, checklist_form },
} =fetch("task.detail", { school_id: this.activeSchoolId, ...httpObj })
this.rightDetail = {
checklists,
students,
school_alias,
checklist_form,
}
this.rightLoading = false
}
复制代码
困扰
如上函数,频繁地点击左侧,运行函数后,会造成详情数据
和loading显示/隐藏
出现问题,同时也浪费了请求的网络资源。
如何在第二次点击获取详情时,取消第一次的获取Promise
?
如何解决取消Promise
的同时处理不同别名的情况?
解析
我们可以这样来想,每个请求接口的别名
都是唯一的,我们需要知道哪个别名正在请求,再取消正在请求的Promise
。
- 创建一个本地的对象,用于储存正在请求的
Promise
。
interface FetchHashMap = {
[aclAlias: string]: Promise<Response>
}
fetchHashMap:FetchHashMap = {}
复制代码
- 新增几个函数来处理
正在请求别名接口的Promise
。
hasFetchData(aclString: string): boolean {
return this.fetchHashMap[aclString] ? this.fetchHashMap[aclString] : false
}
deleteFetchData(aclString: string) {
delete this.fetchHashMap[aclString]
}
setFetchData(aclString: string, promise: Promise<any>) {
this.fetchHashMap[aclString] = promise
return this.fetchHashMap[aclString]
}
复制代码
- 以下代码为上篇文章封装后的请求模块。我们在第
4
步继续改进。
/**
* 简单封装
* @params {string} 路由别名
* @params {Object} 传入参数
* @return Response
**/
async fetch(aclString: string, httpObj?: Object, paramsObj?: Object) {
...
// 首先 根据别名获取路由数据,然后判断是否有路由参数
if (isHaveRouteParams) {
...
if (paramsSuccess) {
const result = await fetchPost(aclString, routeHttpObj, method, postHttpObj, paramsObj)
return result
} else {
console.error(routesArray)
console.error("参数传入不全或者参数value为空。")
}
} else {
// 没有路由参数直接请求
const result = await fetchPost(aclString, {}, method, httpObj, paramsObj)
return result
}
}
复制代码
- 把最后请求
Promise
分离出来。
/**
* 减少相同代码,统一请求接口
* @param {string} aclString 权限acl
* @param {object} routeHttpObj 路由别名参数
* @param {string} method 请求方法
* @param {object} postHttpObj post参数
* @param {object} paramsHttpObj 路由后缀参数
* @return {Promise<Response>} result 请求接口后的返回参数
**/
async fetchPost(aclString: string, routeHttpObj: any, method: string, postHttpObj: any, paramsHttpObj: any): Promise<any> {
let httpUrl: any = Object.keys(routeHttpObj).length ? await this.getApi(aclString, routeHttpObj) : await this.getApi(aclString)
if (paramsHttpObj && Object.keys(paramsHttpObj).length) {
const paramsUrl: string = this.getQueryObject(paramsHttpObj)
httpUrl = `${httpUrl}?${paramsUrl}`
}
const result: any = this.fetchPostWrap(this.http[method](httpUrl, postHttpObj).toPromise())
return result
复制代码
-
给请求函数添加逻辑。
(1). 请求的Promise传入模块。
(2). 查看
fetchHashMap
中是否有当前请求的别名存在?(3). 如果有,那就说明已经有同别名的请求,我们需要把它的
Promise
中断。(4). 如果没有,说明没有同别名的请求
pending
中。(5). 接着处理当前的请求,并正常返回
Promise
。(6). 返回结果之前把
fetchHashMap
中记录的当前请求删除。
async fetchPost(...): Promise<any> {
...
let abort, fetchPromise
//使用Promise.race来取消promise
// 当race()传入参数中有Promise失败或成功就会返回,不管其他的Promise
const result: any = Promise.race([
this.http[method](httpUrl, postHttpObj).toPromise(),
new Promise((resolve, reject) => {
abort = reject
}),
])
// 把race中用于取消Promise的reject拉出来,以便后续调用
result.abort = abort
//当一个请求进来,先判断是否有唯一别名的请求正在请求,如果有那就直接运行abort取消。
if ((fetchPromise = this.hasFetchData(aclString))) {
fetchPromise.abort({})
}
// 记录当前请求的Promise
fetchPromise = this.setFetchData(aclString, result)
// 正常返回请求结果
return fetchPromise
.then((res) => {
// 返回请求Promise时,把正在请求的Promise推出。
this.deleteFetchData(aclString)
return res
})
.catch((error) => error)
}
复制代码
打完收工。
最后
好哥哥学到的话点个赞再走吧。
另外宣传下自己的面试系列,更新中
面试系列第一篇:
面试官:你知道Callback Hell(回调地狱)吗?
面试系列第二篇:
面试官:react和vue有什么区别吗?
面试系列第三篇:
面试官:你了解es6的知识吗?
面试系列第四篇:
面试官:你了解Webpack吗?
面试系列第五篇:
面试官:你使用webpack时手写过loader,分离过模块吗?
面试系列第五篇:
面试官:如何正确的判断Javascript中的数据类型?