开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
前言
最近开始准备使用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