【小程序-慕尚花坊04】网络请求并发与loading

发布于:2025-09-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

如需转载,请附上链接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}))
  }
})

网站公告

今日签到

点亮在社区的每一天
去签到