前言
最近开始准备使用uniapp
做一个app应用,所以就对 uniapp 相关的公共方法进行了封装,本章将对 uniapp
的网络请求uni.request
封装成一个包含各类基础功能的类,方便以后其它项目复用
需求
目标是希望封装一个 http
类,能够实现我们常用的各类请求相关的功能
- 对请求地址的统一配置(方便环境切换)
- 对请求头的统一配置(方便设置 token 等参数)
- 接口请求通用配置(
success/fail/complete
) - 加载遮罩
loading
的简单配置 - 防止重复请求造成竞态问题
实现
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 页签在切换时会调用接口获取列表数据,如果用户快速的操作,由于接口的响应速度是不确定的,就有可能造成先请求,后返回,覆盖了最后一次操作期望的结果
如何解决问题呢,思路也比较简单,维护一个请求的任务列表,如果上一个任务还未响应,又触发了一个相同的接口请求任务时,把上一个请求取消掉就好了
这里需要考虑2个点
- 如何取消请求
uniapp
的request
方法调用时会返回一个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
判断是否需要竞态处理,并在请求完成后,将任务清除,简答搞个按钮快速点击实验下
完成~可以看到,除了最后一次请求成功之前都取消了,这就达到了我们想要的结果,至此,我们一个简单 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