记在实战项目中对于请求模块的无敌封装续集之 如何取消Promise?

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

前言

在上次写完记在实战项目中对于请求模块的无敌封装的文章后,我把一个严重影响用户体验的问题提上了日程。

可以看完上述文章后和我一起改进请求模块。

如何取消Pomrise?

场景

左右页面布局,点击左侧任务列表中的元素,获取右侧任务详情。

对于这种场景中,正常测试下来其实都没有问题,但是频繁地切换点击左侧任务列表,就会有个很严重的体验问题。

由于左侧点击后,运行getTaskDetail函数,逻辑如下。

  1. 打开右侧详情页面的loading
  2. 获取筛选的条件。
  3. 根据点击的任务获取id或其他值请求接口。
  4. 获取接口返回的数据后赋值至页面变量。
  5. 关闭右侧详情页面的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

  1. 创建一个本地的对象,用于储存正在请求的Promise
interface FetchHashMap = {
    [aclAlias: string]: Promise<Response>
}

fetchHashMap:FetchHashMap = {}

复制代码
  1. 新增几个函数来处理正在请求别名接口的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]
}
复制代码
  1. 以下代码为上篇文章封装后的请求模块。我们在第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
    }
}
复制代码
  1. 把最后请求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. 给请求函数添加逻辑。

    (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中的数据类型?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享