微信小程序网络请求封装【附完整代码】

一、request实例方法

js 复制代码
// 创建WxRequest类
// 通过类的方式来进行封装,会让代码更加具有复用性
// 也可以方便添加新的属性和方法
class WxRequest {
  // 用于创建和初始化类的属性和方法
  constructor() { }
  // request 实例方法接收一个对象类型的参数
  // 属性值和wx.request方法调用时传递的参数保持一致
  request(options) {
    // 需要使用Promise封装wx.request处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          resolve(res)
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          reject(err)
        }
      })
    })
  }
}

对WxRequest进行实例化

js 复制代码
// 对WxRequest进行实例化
const instance = new WxRequest()
// 将WxRequest实例暴露出去,方便在其他文件中进行使用
export default instance

按钮绑定事件handler

js 复制代码
<button type="warn" plain bind:tap="handler">测试发送请求</button>

两种方法进行调用

js 复制代码
  async handler() {
    // 第一种调用方法,.then的方式进行调用
    instance.request({
      url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',
      method: "GET"
    }).then((res) => {
      console.log(res)
    })

    // 第二种调用方式,async和await的方式进行调用
    const res = await instance.request({
      url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',
      method: "GET"
    })
    console.log(res)
  },

两种方法都调用请求成功

resolve方法:- resolve 是一个函数,用于在异步操作成功完成时调用,并将操作的结果作为参数传递给 resolve。这将使 Promise 对象变为已解决(fulfilled)状态,并传递结果给 Promise 的 .then() 方法中的回调函数。

reject方法: reject 也是一个函数,用于在异步操作失败时调用,并将错误作为参数传递给 reject。这将使 Promise 对象变为已拒绝(rejected)状态,并传递错误给 Promise 的 .catch() 方法中的回调函数。

二、设置请求参数

请求参数的设置有三种方式:

  1. 默认参数:在WxRequest类中添加defaults实例属性来设置默认值
  2. 实例化时参数:在对WxRequest类进行实例化时传入相关的参数,需要在constructor构造函数形参进行接收
  3. 调用实例方法时传入请求参数

在类内部定义默认的请求参数

js 复制代码
  // 定义实例属性,用来设置默认请求参数
  defaults = {
    // 请求基地址
    baseURL: '',
    // 请求路径
    url: '',
    // 请求参数
    data: null,
    // 默认的请求方法
    method: 'GET',
    // 请求头
    header: {
      // 设置数据的交互格式
      'Content-type': 'application/json'
    },
    // 默认的超时时长,小程序默认的超时时长是1分钟
    timeout: 60000
  }

当用户实例化时会传入相关的参数

js 复制代码
// 对WxRequest进行实例化
// 现在会执行 constructor 中的代码
const instance = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

传入的参数会被constructor形参进行接收,在constructor内部需要来合并请求参数

js 复制代码
  // 用于创建和初始化类的属性和方法
  // 在实例化时传入的参数,会被constructor形参进行接收
  constructor(params = {}) {
    // 从右往左 params覆盖this.defaults,再将this.defaults和{}进行合并
    // 通过Object.assign方法合并请求参数 注意:需要传入的参数,覆盖默认的参数,因此传入的参数需要放到最后
    this.defaults = Object.assign({}, this.defaults, params)
  }

当我们在调用实例方法时,也会传入请求参数

js 复制代码
    // 第二种调用方式,async和await的方式进行调用
    const res = await instance.request({
      url: '/index/findBanner',
      method: "GET"
    })

传入的请求参数需要在实例化方法内部来进行合并

js 复制代码
request(options) {

    // 注意:需要先合并完成的请求地址 baseURL+url
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数
    options = { ...this.defaults, ...options }

    // 需要使用Promise封装wx.request处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          resolve(res)
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          reject(err)
        }
      })
    })
  }
}

最后进行调用测试

js 复制代码
    // 第二种调用方式,async和await的方式进行调用
    const res = await instance.request({
      url: '/index/findBanner',
      method: "GET"
    })
    console.log(res)

发起请求成功

三、封装GET POST DELETE PUT请求快捷方法

js 复制代码
  /* 
      封装GET实例方法
  */
  get(url, data = {}, config = {}) {
    // 需要调用request请求方法发送请求,只需要组织好参数,传递给request请求方法即可
    //  当调用get方法时,需要将request方法的返回值return出去
    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))
  }

  /* 
      封装DELETE实例方法
  */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
  }

  /* 
    封装PUT实例方法
  */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'PUT' }, config))
  }

传入url,data,config进行测试

js 复制代码
    const res = await instance.get('/index/findBanner', { test: 111 }, { timeout: 20000 })
    console.log(res)

测试成功

四、网络请求封装时的注意事项

在使用wx.request发送请求时,只要成功接收到服务器返回的结果,无论statusCode状态码是多少,都会执行success。开发者需要根据业务逻辑,自己来进行相关的判断处理

js 复制代码
handler1() {
    wx.request({
      url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner888',
      method: 'GET',
      success: (res) => {
        // 在使用wx.request发送请求时,只要成功接收到服务器返回的结果
        // 无论statusCode状态码是多少,都会执行success
        // 开发者需要根据业务逻辑,自己来进行相关的判断处理
        console.log('虽然接口出错了,但是我依然会走success')
        console.log(res)
      },
      fail: (err) => {
        console.log(err)
      }
    })
  },

一般在网络出现异常时(网络超时),就会执行fail

五、定义请求---响应拦截器

在WxRequest类内部定义interceptors实例属性,属性中需要包含request以及response方法 需要注意:在发送请求时,还需要区分是否通过实例调用了拦截器

  1. 没有通过实例调用拦截器,需要定义默认拦截器,在默认拦截器中,需要将请求参数进行返回
  2. 通过实例调用拦截器,那么实例调用的拦截器会覆盖默认的拦截器方法,然后将新增或修改的请求参数进行返回

定义默认拦截器

js 复制代码
  // 定义拦截器对象
  // 需要包含请求拦截器以及响应拦截器,方便在请求之前以及相应之后进行逻辑处理
  interceptors = {
    // 请求拦截器
    // 在请求发送之前,对请求参数进行新增或者修改
    request: (config) => config,

    // 响应拦截器
    // 在服务器响应数据以后,对服务器响应的数据进行逻辑处理
    response: (response) => response
  }

通过实例配置请求-响应拦截器

js 复制代码
// 配置请求拦截器
instance.interceptors.request = (config) => {
  // 在请求发送之前做点什么......
  return config
}

// 配置响应拦截器
instance.interceptors.response = (response) => {
  // 对服务器响应数据做点什么......
  return response
}

调用请求-响应拦截器

在请求发送之前,调用请求拦截器,新增和修改请求参数

js 复制代码
  request(options) {

    // 注意:需要先合并完成的请求地址 baseURL+url
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数
    options = { ...this.defaults, ...options }

    // console.log(options)

    // 在请求发送之前,调用请求拦截器,新增和修改请求参数
    options = this.interceptors.request(options)

    // 需要使用Promise封装wx.request处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          resolve(res)
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          reject(err)
        }
      })
    })
  }

不管是成功相响应还是失败响应,都需要调用响应拦截器,响应拦截器需要接收服务器响应的数据,然后对数据进行逻辑处理,处理好以后在返回,然后通过resolve将返回的数据抛出去。 在给响应拦截器传递参数时,需要将请求参数也一起传递,方便进行代码的调试或者进行其他逻辑处理,需要先合并参数,然后将合并的参数传递给响应拦截器。

js 复制代码
wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          const mergeRes = Object.assign({}, res, { config: options })
          resolve(this.interceptors.response(mergeRes))
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          const mergeErr = Object.assign({}, err, { config: options })
          resolve(this.interceptors.response(mergeErr))
          reject(err)
        }
      })

实例中配置的请求拦截器和响应拦截器会覆盖默认请求响应拦截器 不管注不注释实例的拦截器,都可以发送请求成功

六、完善响应拦截器

在响应拦截器,我们需要判断请求成功还是请求失败,然后进行不同的业务逻辑处理。 但是目前不管是请求成功还是请求失败,都会执行响应拦截器。

如何判断?

如果请求成功,将响应成功的数据传递给响应拦截器,同时在传递的数据中新增isSuccess:true字段,表示请求成功

如果请求失败,将响应失败的数据传递给响应拦截器,同时在传递的数据中新增isSuccess:false字段,表示请求失败

在实例调用的响应拦截中,根据传递的数据进行了以下的处理:

如果isSuccess:true 表示服务器响应了结果,我们可以将服务器响应的数据简化以后进行返回

如果isSuccess:false 表示网络超时或其他网络问题,提示 网络异常,同时返回即可

js 复制代码
 wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
          resolve(this.interceptors.response(mergeRes))
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
          resolve(this.interceptors.response(mergeErr))
          reject(err)
        }
      })

在响应拦截器中打印一下response,看一下返回的数据中有没有isSuccess

js 复制代码
// 配置响应拦截器
instance.interceptors.response = (response) => {
  // 对服务器响应数据做点什么......
  console.log(response)
  return response
}

测试发现可以打印

测试一下失败的情况 把timeout改成100

js 复制代码
// 现在会执行 constructor 中的代码
const instance = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 100
})

将网络改成3G 测试发现isSuccess的值为false

针对isSuccess的不同情况,对response的返回结果进行不同处理,当请求成功时,我们真正需要的数据就是response.data

js 复制代码
// 配置响应拦截器
instance.interceptors.response = (response) => {
  // 从response中解构isSuccess
  const { isSuccess,data } = response
  // 如果isSuccess为false,说明执行了fail回调函数
  // 这时候就说明网络异常,需要给用户提示网络异常
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }
  // 对服务器响应数据做点什么......
  // console.log(response)
  // 如果网络请求执行成功到这里,那么我们直接把data进行返回即可
  // return response
  return data
}

七、完善请求、响应拦截器

在发送请求时,购物车列表、收货地址、更新头像等接口,都需要进行权限验证,因此我们需要在请求拦截器中判断本地是否存在访问令牌token,如果存在就需要在请求头中添加token字段

在使用wx.request发送网络请求时,只要成功接收到服务器返回,无论statusCode是多少,都会进入success回调,因此开发者根据业务逻辑对返回值及逆行判断。

后端返回的业务状态码如下:

  1. 业务状态码 === 200,说明接口已请求成功,服务器成功返回了数据
  2. 业务状态码 === 208,说明没有token或者token过期失效,需要登录或者重新登陆
  3. 业务状态码 === 其他,说明请求或者响应出现了异常
js 复制代码
// 配置请求拦截器
instance.interceptors.request = (config) => {
  // 在请求发送之前做点什么......

  // 在发送请求之前,需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  // 如果存在token,就需要在请求头中添加token字段
  if (token) {
    // 这里的['token']是一个属性访问器的语法,它允许你使用字符串作为键(key)来访问或设置对象的属性。
    config.header['token'] = token
  }
  return config
}

在storage中添加token字段,再发请求测试header中是否加上了token

测试成功

在响应拦截器中判断服务器的响应状态码

js 复制代码
// 配置响应拦截器
instance.interceptors.response = async (response) => {
  // 从response中解构isSuccess
  const { isSuccess, data } = response
  // 如果isSuccess为false,说明执行了fail回调函数
  // 这时候就说明网络异常,需要给用户提示网络异常
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }
  // 对服务器响应数据做点什么......
  // console.log(response)

  // 判断服务器响应的业务状态码
  switch (data.code) {
    // 如果后端返回的业务状态码等于200,说明请求成功,服务器成功响应了数据
    case 200:
      // 对服务器响应数据做点什么......
      return data
    // 如果返回的业务状态码等于208,说明没有token,或者token失效
    // 就需要让用户登录或者重新登陆
    case 208:
      const res = await modal({
        content: "鉴权失败,请重新登录",
        showCancel: false // 不显示取消按钮
      })
      if (res) {
        // 消除之前失效的token,同时需要清除本地存储的全部信息
        clearStorage()
        wx.navigateTo({
          url: '/modules/otherModule/pages/login/login.js',
        })
      }
      return Promise.reject(response)
    default:
      toast({
        title: "程序出现异常,请联系客服或稍后重试"
      })
      return Promise.reject(response)
  }

  // 如果网络请求执行成功到这里,那么我们直接把data进行返回即可
  // return response
  return data
}

测试208

在storage中写入一个假的token,运行项目

并且storage中的token被清除

js 复制代码
    const res = await instance.get('/cart/getCartList').catch((err)=>{
      console.log(err)
    })

捕获错误 返回的状态码是208

故意写错url

js 复制代码
    const res = await instance.get('/cart/getCartList2222')
    console.log(res)

八、发起并发请求

前端并发请求是指在前端页面同时向后端发起多个请求的情况。当一个页面需要请求多个接口获取数据时,为了提高页面的加载速度和用户体验,可以同时发起多个请求,这些请求之间就是并发的关系。

我们通过两种方式演示发起多个请求

  1. 使用async和await
  2. 使用promise.all
js 复制代码
  async allHandler() {
    // 演示通过async和await方式同时发起多个请求
    // async和await能够控制异步任务以同步的流程来执行
    await instance.get('/index/findBanner')
    await instance.get('/index/findCategory1')
    await instance.get('/index/findBanner')
    await instance.get('/index/findCategory1')
  },

进度条以台阶的方式进行排列,说明当前的四个请求是按照同步流程来进行发送的 四个请求的时间之和加起来的总时间之后,数据才会进行渲染

以async和await方式发起请求,当第一个请求结束以后,才能发起第二个请求。在前一个请求结束以后,才能够发起下一个请求。会造成请求的阻塞,从而影响页面的渲染速度

js 复制代码
  async allHandler() {
    // 演示通过Promise.all同时发起多个请求
    // Promise.all 能够将多个请求同时进行发送
    await Promise.all([instance.get('/index/findBanner'),instance.get('/index/findCategory1'),instance.get('/index/findBanner'),instance.get('/index/findCategory1')])
  },

Promise.all能够将多个异步请求同时进行发送,也就是并行发送,并不会造成请求的阻塞,从而不会影响页面的渲染速度

封装all方法用来处理并发请求

js 复制代码
  /* 
    封装all方法用来处理并发请求
  */
  all(...promise) {
    console.log(promise)
    //  通过展开运算符接受传递的参数
    // 那么展开运算符会将传入的参数转成数组
    return Promise.all(promise)
  }

调用all方法

js 复制代码
    const res = await instance.all(instance.get('/index/findBanner'),instance.get('/index/findCategory1'),instance.get('/index/findBanner'),instance.get('/index/findCategory1'))
    console.log(res)

九、添加loading效果

在封装时添加loading效果,从而提高用户体验

  1. 在请求发送之前,需要通过wx.showLoading展示loading效果
  2. 当服务器响应数据以后,需要调用wx.hideLoading隐藏loading效果
js 复制代码
  request(options) {

    // 注意:需要先合并完成的请求地址 baseURL+url
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数
    options = { ...this.defaults, ...options }

    // 在请求发送之前,添加loading效果
    wx.showLoading()

    // console.log(options)

    // 在请求发送之前,调用请求拦截器,新增和修改请求参数
    options = this.interceptors.request(options)

    // 需要使用Promise封装wx.request处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
          resolve(this.interceptors.response(mergeRes))
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
          resolve(this.interceptors.response(mergeErr))
          reject(err)
        },
        // 接口调用结束的回调函数(调用成功 失败都会执行)
        complete: () => {
          // 不管请求是成功还是失败,都需要隐藏loading
          wx.hideLoading()
        }
      })
    })
  }

十、完善loading

目前在发送请求时,请求发送之前会展示loading,相应之后会隐藏loading,但是loading的展示和隐藏会存在以下问题:

  1. 每次请求都会执行wx.showLoading(),但是页面中只会显示一个,后面的loading会将前面的覆盖
  2. 同时发起多次请求,只要有一个请求成功响应就会调用wx.hideLoading,导致其他请求还没完成,也不会loading
  3. 请求过快或一个请求在另一个请求后立即触发,这时候会出现loading闪烁问题

我们通过队列的方式解决这三个问题:首先在类中新增一个实例属性queue,初始只是一个空数组

  1. 发起请求之前,判断queue如果是空数组则显示loading,然后立即向queue新增请求标识
  2. 在complete中每次请求成功结束,从queue中移除一个请求标识,queue为空时隐藏loading
  3. 为了解决网络请求过快产生loading闪烁问题,可以使用定时器来做判断即可。

定义队列数组

js 复制代码
  // 定义数组队列
  // 初始值需要是一个空数组,用来存储请求队列\存储请求标识
  queue = []
js 复制代码
// request 实例方法接收一个对象类型的参数
  // 属性值和wx.request方法调用时传递的参数保持一致
  request(options) {
    // 如果有新的请求,就清除上一次的定时器
    this.timeId && clearTimeout(this.timeId)

    // 注意:需要先合并完成的请求地址 baseURL+url
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数
    options = { ...this.defaults, ...options }

    // 在请求发送之前,添加loading效果
    // wx.showLoading()

    // 判断queue队列是否为空,如果是空,就显示loading
    // 如果不是空,就不显示loading,不调用wx.showLoading()
    this.queue.length === 0 && wx.showLoading()
    // 然后立即向queue数组队列中添加请求标识
    // 每个标识代表是一个请求,标识是自定义的
    this.queue.push('request')

    // console.log(options)

    // 在请求发送之前,调用请求拦截器,新增和修改请求参数
    options = this.interceptors.request(options)

    // 需要使用Promise封装wx.request处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
          resolve(this.interceptors.response(mergeRes))
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
          resolve(this.interceptors.response(mergeErr))
          reject(err)
        },
        // 接口调用结束的回调函数(调用成功 失败都会执行)
        complete: () => {
          // 不管请求是成功还是失败,都需要隐藏loading
          // wx.hideLoading()

          // 在每一个请求结束以后,都会执行complete回调函数
          // 每次从queue队列中删除一个标识
          this.queue.pop()

          this.queue.length === 0 && this.queue.push('request')
          this.timeId = setTimeout(() => {
            this.queue.pop()
            // 在删除标识之后,需要判断目前queue数组是否为空
            // 如果是空,说明并发请求完成了
            // 就需要隐藏loading,要调用wx.hideLoading()
            this.queue.length === 0 && wx.hideLoading()
            clearTimeout(thie.timeId)
          }, 1);
        }
      })
    })
  }

十一、控制loading的显示

在我们封装的网络请求文件中,通过wx.showLoading默认显示了loading效果

但在实际开发中,有的接口可能不需要显示loading效果,或者开发者希望自己来控制loading的样式与交互,那么就需要关闭默认loading效果。

这时我们就需要一个开关来控制loading显示

  1. 类内部设置默认请求cabs关于isLoading属性,默认值是true,在类内部根据isLoading属性做判断即可
  2. 某个接口不需要显示loading效果,可以在发送请求的时候,可以新增请求配置isLoading设置为false
  3. 整个项目都不需要显示loading效果,可以在实例化的时候,传入isLoading配置为false

增加Loading属性

js 复制代码
  // 定义实例属性,用来设置默认请求参数
  defaults = {
    // 请求基地址
    baseURL: '',
    // 请求路径
    url: '',
    // 请求参数
    data: null,
    // 默认的请求方法
    method: 'GET',
    // 请求头
    header: {
      // 设置数据的交互格式
      'Content-type': 'application/json'
    },
    // 默认的超时时长,小程序默认的超时时长是1分钟
    timeout: 60000,
    // 控制是否适用默认的loading,默认值是true 表示使用默认的loading
    isLoading: true
  }
js 复制代码
    if (options.isLoading) {
      // 判断queue队列是否为空,如果是空,就显示loading
      // 如果不是空,就不显示loading,不调用wx.showLoading()
      this.queue.length === 0 && wx.showLoading()
      // 然后立即向queue数组队列中添加请求标识
      // 每个标识代表是一个请求,标识是自定义的
      this.queue.push('request')
    }
js 复制代码
        // 接口调用结束的回调函数(调用成功 失败都会执行)
        complete: () => {
          if (options.isLoading) {
            // 不管请求是成功还是失败,都需要隐藏loading
            // wx.hideLoading()

            // 在每一个请求结束以后,都会执行complete回调函数
            // 每次从queue队列中删除一个标识
            this.queue.pop()

            this.queue.length === 0 && this.queue.push('request')
            this.timeId = setTimeout(() => {
              this.queue.pop()
              // 在删除标识之后,需要判断目前queue数组是否为空
              // 如果是空,说明并发请求完成了
              // 就需要隐藏loading,要调用wx.hideLoading()
              this.queue.length === 0 && wx.hideLoading()
              clearTimeout(thie.timeId)
            }, 1);
          }
        }

设置某个接口不要Loading效果

js 复制代码
    const res = await instance.get('/index/findBanner',null,{
      isLoading:false
    })
    console.log(res)

整个项目中都不想使用默认的Loading

js 复制代码
const instance = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000,
  isLoading: false
})

如果整个项目都不使用Loading但是在某个接口要使用Loading

js 复制代码
    const res = await instance.get('/index/findBanner',null,{
      isLoading:true
    })
    console.log(res)

十二、接口调用

js 复制代码
// 导入封装的网络请求模块实例
import http from '../utils/http'
export const reqSwiperData = () => {
  return http.get('/index/findBanner')
}
js 复制代码
import {reqSwiperData} from '../../api/index'
  async handler() {
    const res = await reqSwiperData()
    console.log(res)
  },

十三、完整代码

request.js

js 复制代码
// 创建WxRequest类
// 通过类的方式来进行封装,会让代码更加具有复用性
// 也可以方便添加新的属性和方法
class WxRequest {

  // 定义实例属性,用来设置默认请求参数
  defaults = {
    // 请求基地址
    baseURL: '',
    // 请求路径
    url: '',
    // 请求参数
    data: null,
    // 默认的请求方法
    method: 'GET',
    // 请求头
    header: {
      // 设置数据的交互格式
      'Content-type': 'application/json'
    },
    // 默认的超时时长,小程序默认的超时时长是1分钟
    timeout: 60000,
    // 控制是否适用默认的loading,默认值是true 表示使用默认的loading
    isLoading: true
  }

  // 定义拦截器对象
  // 需要包含请求拦截器以及响应拦截器,方便在请求之前以及相应之后进行逻辑处理
  interceptors = {
    // 请求拦截器
    // 在请求发送之前,对请求参数进行新增或者修改
    request: (config) => config,

    // 响应拦截器
    // 在服务器响应数据以后,对服务器响应的数据进行逻辑处理
    response: (response) => response
  }

  // 定义数组队列
  // 初始值需要是一个空数组,用来存储请求队列\存储请求标识
  queue = []


  // 用于创建和初始化类的属性和方法
  // 在实例化时传入的参数,会被constructor形参进行接收
  constructor(params = {}) {
    // 从右往左 params覆盖this.defaults,再将this.defaults和{}进行合并
    // 通过Object.assign方法合并请求参数 注意:需要传入的参数,覆盖默认的参数,因此传入的参数需要放到最后
    this.defaults = Object.assign({}, this.defaults, params)
  }
  // request 实例方法接收一个对象类型的参数
  // 属性值和wx.request方法调用时传递的参数保持一致
  request(options) {
    // 如果有新的请求,就清除上一次的定时器
    this.timeId && clearTimeout(this.timeId)

    // 注意:需要先合并完成的请求地址 baseURL+url
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数
    options = { ...this.defaults, ...options }

    // 在请求发送之前,添加loading效果
    // wx.showLoading()

    if (options.isLoading) {
      // 判断queue队列是否为空,如果是空,就显示loading
      // 如果不是空,就不显示loading,不调用wx.showLoading()
      this.queue.length === 0 && wx.showLoading()
      // 然后立即向queue数组队列中添加请求标识
      // 每个标识代表是一个请求,标识是自定义的
      this.queue.push('request')
    }

    // console.log(options)

    // 在请求发送之前,调用请求拦截器,新增和修改请求参数
    options = this.interceptors.request(options)

    // 需要使用Promise封装wx.request处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        // ...options 是 ES6 的扩展运算符,它将 options 对象的所有属性展开并传递给 wx.request
        ...options,
        // 当接口调用成功时会触发success回调函数, 使用 resolve 返回成功的结果
        success: (res) => {
          const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
          resolve(this.interceptors.response(mergeRes))
        },
        // 当接口调用失败时会触发fail回调函数, 使用 reject 返回错误信息。
        fail: (err) => {
          const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
          resolve(this.interceptors.response(mergeErr))
          reject(err)
        },
        // 接口调用结束的回调函数(调用成功 失败都会执行)
        complete: () => {
          if (options.isLoading) {
            // 不管请求是成功还是失败,都需要隐藏loading
            // wx.hideLoading()

            // 在每一个请求结束以后,都会执行complete回调函数
            // 每次从queue队列中删除一个标识
            this.queue.pop()

            this.queue.length === 0 && this.queue.push('request')
            this.timeId = setTimeout(() => {
              this.queue.pop()
              // 在删除标识之后,需要判断目前queue数组是否为空
              // 如果是空,说明并发请求完成了
              // 就需要隐藏loading,要调用wx.hideLoading()
              this.queue.length === 0 && wx.hideLoading()
              clearTimeout(this.timeId)
            }, 1);
          }
        }
      })
    })
  }

  /* 
      封装GET实例方法
  */
  get(url, data = {}, config = {}) {
    // 需要调用request请求方法发送请求,只需要组织好参数,传递给request请求方法即可
    //  当调用get方法时,需要将request方法的返回值return出去
    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))
  }

  /* 
      封装DELETE实例方法
  */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
  }

  /* 
    封装PUT实例方法
  */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'PUT' }, config))
  }

  /* 
    封装all方法用来处理并发请求
  */
  all(...promise) {
    console.log(promise)
    //  通过展开运算符接受传递的参数
    // 那么展开运算符会将传入的参数转成数组
    return Promise.all(promise)
  }
}

export default WxRequest

http.js

js 复制代码
import WxRequest from './request'
import { getStorage, clearStorage } from './storage'
import { modal, toast } from './extendApi'
// 对WxRequest进行实例化
// 现在会执行 constructor 中的代码
const instance = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000,
  isLoading: false
})

// 配置请求拦截器
instance.interceptors.request = (config) => {
  // 在请求发送之前做点什么......

  // 在发送请求之前,需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  // 如果存在token,就需要在请求头中添加token字段
  if (token) {
    // 这里的['token']是一个属性访问器的语法,它允许你使用字符串作为键(key)来访问或设置对象的属性。
    config.header['token'] = token
  }
  return config
}

// 配置响应拦截器
instance.interceptors.response = async (response) => {
  // 从response中解构isSuccess
  const { isSuccess, data } = response
  // 如果isSuccess为false,说明执行了fail回调函数
  // 这时候就说明网络异常,需要给用户提示网络异常
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }
  // 对服务器响应数据做点什么......
  // console.log(response)

  // 判断服务器响应的业务状态码
  switch (data.code) {
    // 如果后端返回的业务状态码等于200,说明请求成功,服务器成功响应了数据
    case 200:
      // 对服务器响应数据做点什么......
      return data
    // 如果返回的业务状态码等于208,说明没有token,或者token失效
    // 就需要让用户登录或者重新登陆
    case 208:
      const res = await modal({
        content: "鉴权失败,请重新登录",
        showCancel: false // 不显示取消按钮
      })
      if (res) {
        // 消除之前失效的token,同时需要清除本地存储的全部信息
        clearStorage()
        wx.navigateTo({
          url: '/modules/otherModule/pages/login/login',
        })
      }
      return Promise.reject(response)
    default:
      toast({
        title: "程序出现异常,请联系客服或稍后重试"
      })
      return Promise.reject(response)
  }

  // 如果网络请求执行成功到这里,那么我们直接把data进行返回即可
  // return response
  return data
}

// 将WxRequest实例暴露出去,方便在其他文件中进行使用
export default instance
相关推荐
甜甜的资料库2 小时前
基于小程序老人监护管理系统源码数据库文档
微信小程序
Uyker18 小时前
微信小程序动态效果实战指南:从悬浮云朵到丝滑列表加载
前端·微信小程序·小程序
happyCoder1 天前
uniapp 微信小程序实现定时消息订阅提醒(前后端)
微信小程序
Uyker1 天前
从零开始制作小程序简单概述
前端·微信小程序·小程序
打小就很皮...2 天前
HBuilder 发行Android(apk包)全流程指南
前端·javascript·微信小程序
前端缘梦2 天前
微信小程序登录方案实践-从账号体系到用户信息存储
前端·微信小程序
coding随想2 天前
2025年小程序开发全解析:技术储备、行业趋势与实战案例
微信小程序
Nueuis3 天前
微信小程序前端面经
前端·微信小程序·小程序
轩1153 天前
实现仿中国婚博会微信小程序
微信小程序·小程序
知否技术3 天前
2025微信小程序开发实战教程(一)
前端·微信小程序