uniapp 请求方法封装防止竞态问题

简介: uniapp 请求方法封装防止竞态问题

f8c23d713d733194aba31cf263b94d9.png

前言

最近开始准备使用uniapp做一个app应用,所以就对 uniapp 相关的公共方法进行了封装,本章将对 uniapp 的网络请求uni.request封装成一个包含各类基础功能的类,方便以后其它项目复用

需求

目标是希望封装一个 http 类,能够实现我们常用的各类请求相关的功能

  • 对请求地址的统一配置(方便环境切换)
  • 对请求头的统一配置(方便设置 token 等参数)
  • 接口请求通用配置(success/fail/complete
  • 加载遮罩loading的简单配置
  • 防止重复请求造成竞态问题

458211aef00aac4043ddfcb5cd40318.png

实现

uniapp 提供了uni.request 方法来实现各种请求,我们在基于 request 的基础上封装一个类,在该类实例化的时候,把基础配置初始化好

编写 Http 类

class Http {
    constructor(options) {
        this.baseUrl = options.baseUrl,
        this.header = options.header,
        this.requestTaskList = []
        this.codeConfig = {
            '200': () => {}
        }
    }
    request = (opt) => {
        // 请求方法
        // 传入相关的接口参数 opt
    }
}
export default Http
复制代码

初始化相关属性

  • baseUrl:服务地址
  • header:请求头配置
  • requestTaskList:请求任务列表(用来处理请求竞态
  • codeConfig:请求状态处理配置
  • request:暴露出去的请求方法

编写 request 方法

request = (opt) => {
        const {
            baseUrl,
            url,
            data,
            method,
            header,
            dataType,
            success,
            fail,
            complete,
        } = opt
        const host = baseUrl || this.baseUrl
        uni.request({
            url: host + url,
            data: data || {},
            method: method || 'POST',
            header: header || this.header,
            dataType: dataType || 'json',
            success: (res) => {
                success && success(res)
            },
            fail: (err) => {
                fail && fail(err)
            },
            complete: () => {
                complete && complete()
            }
        })
    }
复制代码

我们统一把 uni.request的通用参数提取出来,如果传入参数opt存在对应的参数,使用 opt 的配置,如果,没有,则使用默认配置

状态码处理

codeConfig中对各种需要业务处理的状态码配置上对应方法,在回调中调用

class Http {
    constructor(options) {
        // ...
        this.codeConfig = {
            '200': () => {}
        }
    }
    request = (opt) => {
        // ...
        uni.request({
            // ...
            success: (res) => {
                this.handleCode(res)
                success && success(res)
            }
        })
    }
    handleCode = (res) => {
        const code = res.data.code
        code && this.codeConfig[code]() 
    }
}
复制代码

简单使用

为了方便使用,项目中选择将实例化的对象挂载在 uni 对象上,方便各个页面调用

  • main.js配置
const http = new Http({
        baseUrl: 'https://mcs.snssdk.com',
        header: {
            token: uni.getStorageSync('token') || ''
        }
    })
    uni.$http = http
复制代码
  • 页面使用
methods: {
        test() {
            uni.$http.request({
                url: '/list',
                success: (res) => {
                    console.log(res);
                }
            })  
        }
    }
复制代码

这时我们一个简单的公共请求方法就完成了,下面来继续完善

加载遮罩

算是一个简单的扩展,提供一个思路,后续有类似的需求都可以通过类似的方式进行完善

传参数时添加 loading 参数,并加入显示遮罩的方法,并在请求完成时关闭

request = (opt) => {
        const {
            // ...
            loading,
        } = opt
        // ...
        if (loading) {
            uni.showLoading()
        }
        uni.request({
            // ...
            complete: () => {
                if (loading) {
                    uni.showLoading()
                }
                complete && complete()
            }
        })
    }
复制代码

请求竞态分析

什么是竞态?

首先竞态的出现一般是使用了公共的变量,其次是多个方法操作(异步操作)同一变量所造成的结果不确定性

举个简单的例子,一个 tab 页签在切换时会调用接口获取列表数据,如果用户快速的操作,由于接口的响应速度是不确定的,就有可能造成先请求,后返回,覆盖了最后一次操作期望的结果

4711e6bb8c36458a54195f2e594e63d.png

如何解决问题呢,思路也比较简单,维护一个请求的任务列表,如果上一个任务还未响应,又触发了一个相同的接口请求任务时,把上一个请求取消掉就好了

这里需要考虑2个点

  • 如何取消请求

uniapprequest方法调用时会返回一个requestTask对象,调用对象上的 abort 方法即可取消请求

如果希望返回一个 requestTask 对象,需要至少传入 success / fail / complete 参数中的一个。例如:

var requestTask = uni.request({
    url: 'https://www.example.com/request', //仅为示例,并非真实接口地址。
    complete: ()=> {}
});
requestTask.abort();
复制代码
  • 任务列表如何维护比较合理

根据项目的需要,采用全局捕获请求任务或者以配置形式对有需要的请求做特殊处理,一般不建议使用全部检测,通常都会有一些页可能初始化时会调一些,同样的接口,只是参数不同,这时如果根据接口地址进行拦截,会把正常的业务流程打乱

本项目采用传入任务 id 的形式,只对有传参的接口进行处理

请求竞态处理

// 维护任务列表
    this.requestTaskList = []
    // 任务处理
    handleTask = (requestTask, taskId) => {
        const task = {
            id: taskId,
            requestTask,
        }
        this.requestTaskList.forEach((item, index) => {
            if (item.id === task.id) {
                // 存在相同任务则取消并移除
                item.requestTask.abort()
                this.requestTaskList.splice(index, 1)
                return
            }
        })
        this.addTask(task)
    }
    // 添加任务
    addTask = (task) => {
        this.requestTaskList.push(task)
    }
    // 移除任务
    removeTask = (id) => {
        this.requestTaskList.forEach((item, index) => {
            if (item.id === id) {
                this.requestTaskList.splice(index, 1)
                return
            }
        })
    }
    // 清空任务列表
    clearTaskList = () => {
        this.requestTaskList = []
    }
复制代码

通过是否传入 taskId 判断是否需要竞态处理,并在请求完成后,将任务清除,简答搞个按钮快速点击实验下

9db2aa518094fae856587e216f38f7e.png

完成~可以看到,除了最后一次请求成功之前都取消了,这就达到了我们想要的结果,至此,我们一个简单 uniapp 请求封装就基本完成啦,下面是完整代码,大家有什么更好的思路也欢迎留言提提意见

完整代码

class Http {
    constructor(options) {
        this.baseUrl = options.baseUrl,
        this.header = options.header,
        this.requestTaskList = []
        this.codeConfig = {
            '200': () => {}
        }
    }
    request = (opt) => {
        const {
            baseUrl,
            url,
            data,
            method,
            header,
            dataType,
            loading,
            success,
            fail,
            complete,
            taskId
        } = opt
        const host = baseUrl || this.baseUrl
        if (loading) {
            uni.showLoading()
        }
        const requestTask = uni.request({
            url: host + url,
            data: data || {},
            method: method || 'POST',
            header: header || this.header,
            dataType: dataType || 'json',
            success: (res) => {
                if (taskId) {
                    this.removeTask(taskId)
                }
                this.handleCode(res)
                success && success(res)
            },
            fail: (err) => {
                fail && fail(err)
            },
            complete: () => {
                if (loading) {
                    uni.showLoading()
                }
                complete && complete()
            }
        })
        if (taskId) {
            this.handleTask(requestTask, taskId)
        }
    }
    handleCode = (res) => {
        const code = res.data.code
        code && this.codeConfig[code]() 
    }
    handleTask = (requestTask, taskId) => {
        const task = {
            id: taskId,
            requestTask,
        }
        this.requestTaskList.forEach((item, index) => {
            if (item.id === task.id) {
                item.requestTask.abort()
                this.requestTaskList.splice(index, 1)
                return
            }
        })
        this.addTask(task)
    }
    addTask = (task) => {
        this.requestTaskList.push(task)
    }
    removeTask = (id) => {
        this.requestTaskList.forEach((item, index) => {
            if (item.id === id) {
                this.requestTaskList.splice(index, 1)
                return
            }
        })
    }
    clearTaskList = () => {
        this.requestTaskList = []
    }
}
export default Http
相关文章
|
6月前
uniApp常用功能封装
uniApp常用功能封装
51 0
|
6月前
|
数据处理 开发者
【Uniapp 专栏】提升 Uniapp 开发效率的进阶方法
【5月更文挑战第17天】提升Uniapp开发效率的关键包括组件化、模板语法、数据处理和代码组织。通过封装组件如通用按钮,利用列表渲染生成多个元素,使用计算属性和方法处理复杂逻辑,以及采用预处理器如Sass编写样式。此外,良好的代码结构和使用开发者工具进行调试也是重要环节。掌握这些进阶技巧能帮助开发者更高效地构建高质量应用。
118 2
【Uniapp 专栏】提升 Uniapp 开发效率的进阶方法
|
4月前
uniapp实战 —— 弹出层 uni-popup (含vue3子组件调父组件的方法)
uniapp实战 —— 弹出层 uni-popup (含vue3子组件调父组件的方法)
482 1
|
4月前
uniapp实战 —— 轮播图【数字下标】(含组件封装,点击图片放大全屏预览)
uniapp实战 —— 轮播图【数字下标】(含组件封装,点击图片放大全屏预览)
88 1
|
4月前
uniapp实战 —— 轮播图【自定义指示点】(含组件封装,自动注册全局组件,添加全局组件类型声明)
uniapp实战 —— 轮播图【自定义指示点】(含组件封装,自动注册全局组件,添加全局组件类型声明)
226 1
|
4月前
uniapp【组件封装】时间戳格式化为星期
uniapp【组件封装】时间戳格式化为星期
77 0
|
6月前
|
移动开发 前端开发 JavaScript
uniapp中IO模块(管理本地文件系统)的常用功能封装
uniapp中IO模块(管理本地文件系统)的常用功能封装
561 1
|
6月前
|
移动开发 小程序 API
uniapp组件库Line 线条 的适用方法
uniapp组件库Line 线条 的适用方法
307 0
|
6月前
|
移动开发 小程序 iOS开发
uniapp组件库fullScreen 压窗屏的适用方法
uniapp组件库fullScreen 压窗屏的适用方法
255 1
|
6月前
|
SQL 开发框架 数据库连接
uniapp中sqlite数据库常用操作的简单封装
uniapp中sqlite数据库常用操作的简单封装
626 0