网络请求并发与loading
如需转载,请附上链接https://blog.csdn.net/m0_59269218/article/details/151002956
一,网络请求并发与loading
一,网络请封装-响应拦截器
本篇文章依旧是慕尚花坊的初始化篇网络请求拦截器的封装,本文主要讲解的是网络请求的并发处理,以及对loading的封装。由于这篇文章要写的内容是基于前两篇内容的,因此可以先看前两篇文章或者直接拉去gitee残酷代码即可
在学习本系列之前,再次先提供一下尚硅谷给的接口文档和笔记资料,以及自己的仓库地址
接口文档:https://s.apifox.cn/6ed6c5c4-56c4-4619-8e2a-4817aa140e30
笔记资料:https://www.yuque.com/bigweb/dpa5f7/dm2ygniwxksuxy6o
学习视频对应链接:https://www.bilibili.com/video/BV1LF4m1E7kB
本系列个人的gitee仓库地址:https://gitee.com/zhenghuisheng/mu-shanghua-fang
1,并发处理
1.1,异步实现方式
并发处理一般指的是服务器同时接收到多个请求的处理,前端的并发请求指的是同一时间向后端发起多个请求,比如打开某个页面时,需要同时的拿后端多个数据的列表,那么这些请求之间就是并发关系
实现异步的方式主要有两种,一种是直接通过async+await的方式实现异步,一种是直接通过Promise.all的方式实现异步。如果两个是发起单个请求,二者区别不大,如果是发起多个异步请求,那么二者之间就会存在差异
如果直接使用async+await 的方式实现异步,其案例如下,那么在多个并发请求时使用这种方式,由于每个请求需要等待结果返回,那么就会造成请求的阻塞,需要上一个请求那道结果之后,才能继续进行下一个请求,由于阻塞的原因,又相当于将异步变成了同步
async sendRequest() {
await instance.get("/index/findBanner")
await instance.get("/index/findCategory1")
await instance.get("/index/findBanner")
await instance.get("/index/findCategory1")
}
点击请求之后,可以发现四个请求依次执行的,需要等上一次请求完成之后再完成下一个请求,因此在多个请求的并发处理场景方面,其效率不高,结合Time和Waterfall两个相应参数可以看出这四个请求需要花费的时间为 233+93+94+91=511ms
接下来看第二种异步请求方式,通过 Promise.all 的方式实现异步请求,其案例如下,直接将请求丢到数组里面即可
await Promise.all([instance.get("/index/findBanner"),instance.get("/index/findCategory1"),
instance.get("/index/findBanner"),instance.get("/index/findCategory1")])
点击请求之后,其相应如下,结合Waterfall和Time可以看出,总共花费就是最大的那个时间,总共花费333ms,比上面的511ms效率快了很多。
2.2,Promise.all异步方式封装
经过测试,可以发现Promise.all的异步请求方式是高于asyns+await的方式的,因此后续的异步方式同意通过这个Promise.all的方式进行异步调用。
那么只需要在request.js文件中,封装一个全局使用的异步请求即可,其代码如下
// 封装一个异步处理请求
all(...promise){
return Promise.all(promise)
}
那么在这个test.js文件中,直接使用这个全局封装的异步处理即可
import instance from '../../utils/request'
Page({
// 点击按钮触发 handler 方法
async sendRequest() {
// 方式三
await instance.all(instance.get("/index/findBanner"),instance.get("/index/findCategory1"),
instance.get("/index/findBanner"),instance.get("/index/findCategory1"))
}
})
2,loading加载
2.1,loading的基本使用
在项目开发中,在列表渲染前调用后端的列表接口一般都需要通过loading加载一会,比如下拉刷新等,因此也可以在这个request.js文件中最这个loading进行封装,其思路如下,在请求发送前开始加载loading,在想要结束之后关闭这个loading
// 服务发送之前,添加loading效果
wx.showLoading()
// 服务器响应后,隐藏loading效果
complete: () => {
wx.hideLoading()
}
2.2,loading与并发结合案例
如果在并发请求中需要展示和关闭loading,那么就可能出现前一个以及展示完的loading会把后面要展示的loading给关闭了,或者说后面请求的loading先执行完将前面的请求loading给覆盖了的情况,或者可能出现闪烁的问题。
那么为了解决这种并发的问题,可以直接通过队列的方式来进行解决,其核心代码如下,新增一个queue队列,让后在请求前往队列中push一个request字符串
// 定义 constructor 构造函数,用于创建和初始化类的属性和方法
constructor(params = {}) {
...
// 初始化数组
this.queue = []
}
在发送请求之前判断队列长度是否为0,为0就开启loading,并且往队列中push一个字符串
// 发送请求之前添加 loding
this.queue.length === 0 && wx.showLoading()
// 然后想队列中添加 request 标识,代表需要发送一次新请求
this.queue.push('request')
在响应回调后再判断队列是否为空,并且在每个complete回调中需要设置一个定时器,并且在完成之后需要将删除
complete: () => {
// 每次请求结束后,从队列中删除一个请求标识
this.queue.pop()
// 如果队列已经清空,在往队列中添加一个标识
this.queue.length === 0 && this.queue.push('request')
// 等所有的任务执行完以后,经过 100 毫秒
// 将最后一个 request 清除,然后隐藏 loading
this.timerId = setTimeout(() => {
this.queue.pop()
this.queue.length === 0 && wx.hideLoading()
}, 100)
}
2.3,loading控制
上面是在全局的js文件中使用了loading的加载,也就是说不管外部想不想用这个loading,发起请求时都会进行这个loading的加载,为了解决这个问题,可以将这个选择控制权交给外部自定义,如果外部不定义也可以给一个默认参数进行全局控制,比如默认就是打开loading的加载和关闭
首先是在 WxRequest 类的defaults属性中,增加一个isLoading的属性,给默认值为true
class WxRequest {
// 初始化默认的请求属性
defaults = {
isLoading: true // 是否显示 loading 提示框
}
}
如果外部不想使用这个isLoading,那么在外部初始化这个对象的时候,可以对这个参数进行值的设置
// 对 WxRequest 进行实例化
const instance = new WxRequest({
baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
isLoading: false // 隐藏 loading
})
那么在使用loading的时候,又需要判断是否需要使用loading,需要的话再操作队列
// 发送请求之前添加 loding
if (options.isLoading) {
this.queue.length === 0 && wx.showLoading()
// 然后想队列中添加 request 标识,代表需要发送一次新请求
this.queue.push('request')
}
在complete回调中,也需要判断是否需要使用loading,如果不需要则直接提前返回
complete: () => {
if (!options.isLoading) return
}
3,并发配合loading完整代码
在request.js文件中的全部代码如下,增加了并发控制和loading,并且对并发控制和loading进行了结合使用,loading的控制权也可以交给外部自行控制和定义
class WxRequest {
//url表示请求的接口,data表示query的参数,method表示请求的方法,config表示需要覆盖的默认参数
get(url, data = {}, config = {}) {
return this.request(Object.assign({
url,
data,
method: "GET"
}, config))
}
// 封装 POST 实例方法
post(url, data = {}, config = {}) {
return this.request(Object.assign({
url,
data,
method: 'POST'
}, config))
}
// 封装 PUT 实例方法
put(url, data = {}, config = {}) {
return this.request(Object.assign({
url,
data,
method: 'PUT'
}, config))
}
// 封装 DELETE 实例方法
delete(url, data = {}, config = {}) {
return this.request(Object.assign({
url,
data,
method: 'DELETE'
}, config))
}
// 定义默认拦截器
interceptors = {
// 请求拦截器
request: (config) => config,
// 响应拦截器
response: (response) => response
}
// 全局异步处理
all(...promise) {
return Promise.all(promise)
}
// 默认参数对象
defaults = {
baseURL: '', // 请求基准地址
url: '', // 开发者服务器接口地址
data: null, // 请求参数
method: 'GET', // 默认请求方法
header: { // 请求头
'Content-type': 'application/json' // 设置数据的交互格式
},
timeout: 60000, // 小程序默认超时时间是 60000,一分钟,
isLoading: true // 是否显示 loading 提示框
}
// 定义 constructor 构造函数,用于创建和初始化类的属性和方法
constructor(params = {}) {
// 在实例化时传入的参数能够被 constructor 进行接收
console.log(params)
// 使用 Object.assign 合并默认参数以及传递的请求参数
this.defaults = Object.assign({}, this.defaults, params)
// 初始化数组
this.queue = []
}
/**
* @description 发起请求的方法
* @param { Object} options 请求配置选项,同 wx.request 请求配置选项
* @returns Promise
*/
request(options) {
// 拼接完整的请求地址
options.url = this.defaults.baseURL + options.url
// 合并请求参数
options = {
...this.defaults,
...options
}
// 服务器发送之前调用请求拦截器
options = this.interceptors.request(options)
if (options.isLoading) {
// 发送请求之前添加 loding
this.queue.length === 0 && wx.showLoading()
// 然后想队列中添加 request 标识,代表需要发送一次新请求
this.queue.push('request')
}
// 使用 Promise 封装异步请求
return new Promise((resolve, reject) => {
// 使用 wx.request 发起请求
wx.request({
...options,
// 接口调用成功的回调函数
success: (res) => {
// 合并请求参数和响应参数,方便调试
const mergrRes = Object.assign({}, res, { config: options, isSuccess: true });
// 响应后调用响应拦截器
resolve(this.interceptors.response(mergrRes))
},
// 接口调用失败的回调函数
fail: (err) => {
// 合并请求参数和响应参数,方便调试
const mergrErr = Object.assign({}, err, { config: options, isSuccess: false });
// 响应后调用响应拦截器
reject(this.interceptors.response(mergrErr))
},
complete: () => {
// 如果不展示,则直接返回
if (!options.isLoading) return
// 每次请求结束后,从队列中删除一个请求标识
this.queue.pop()
// 如果队列已经清空,在往队列中添加一个标识
this.queue.length === 0 && this.queue.push('request')
// 等所有的任务执行完以后,经过 100 毫秒
// 将最后一个 request 清除,然后隐藏 loading
this.timerId = setTimeout(() => {
this.queue.pop()
this.queue.length === 0 && wx.hideLoading()
}, 100)
}
})
})
}
}
// 将 WxRequest 的实例通过模块化的方式暴露出去
export default WxRequest
可以在http.js中的isLoading设置成false来进行测试,直接将isLoading设置成false
const instance = new WxRequest({
baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
timeout: 15000,
isLoading: false
})
或者也可以直接在请求这里直接设置isLoading的值,比如在test.js文件的请求中,设置isLoading的值为false
import instance from '../../utils/http'
Page({
// 点击按钮触发 handler 方法
async handler() {
await instance.all(instance.get("/index/findBanner",{},{isLoading:false}))
}
})