慕尚花坊 --- 通用模块封装

0. 为什么模块封装

在项目开发的时候, 经常会频繁的使用到一些 API, 例如: wx.showToast()wx.showModal() 等消息提示 API,每次使用这些小程序提供的原生 API, 会导致代码冗余,所以将其封装成公共方法来简化 API 的调用

1. 消息提示框

1. 封装思路

  1. 创建一个 toast()wx.showToast() 进行封装
  2. 调用该方法时, 传递对象作为参数
    1. 如果没有传递任何参数, 则使用默认的空对象 {} 作为参数
    2. 对象中包含 titleicondurationmask 参数,并设置默认值
  3. 在需要显示提示框的时候, 直接调用 toast(), 并传入相关参数
    1. 不传递参数, 使用默认值
    2. 传入部分参数, 覆盖默认参数
  4. 封装后的提示框有两种调用方式
    1. 模块化的导入使用
    2. 将对象挂载到 全局 wx 示例对象身上, 可以通过 wx.toast() 来调用提示框

2. 开始封装

miniprogram/utils/extendAPI.js

const toast = ({
  title = "数据加载中...",
  icon = "none",
  duration = 2000,       // 如果用户传入了参数, 在形参位置,通过解构的方式获取用户传入的参数,同时设置默认值
  mask = true
} = {}) => {
  wx.showToast({
    title: title, // 需要显示的提示框标题
    icon: icon, // 提示框的图标, success(成功)、error(失败)、loading(加载)、none(不显示图标)
    duration: duration, // 提示框的消失时间
    mask: mask // 是否显示透明遮罩层,防止触摸穿透(可以继续点页面中的其他内容)
  })
}

wx.toast = toast   // 将 toast 挂载在 wx 实例对象身上
export { toast }

模块化导入调用

import { toast } from '../../utils/extendAPI'

Page({
    // 不传参数, 使用默认值
    toast()
    
    // 传入部分参数, 覆盖默认值
    toast({
        title: '数据加载成功',
        icon: 'success'
    })
    
    
     // 通过 wx 实例对象调用
    wx.modal()
})

全局 wx 实例对象调用

Page({
    // 不传参数, 使用默认值
    wx.modal()

	// 传入部分参数, 覆盖默认值
    wx.toast({
        title: '数据加载成功',
        icon: 'success'
    })
})

2. 模态对话框

wx.showModal() 通常用来向用户询问是否执行一些操作, 如: 询问用户是否退出、是否删除等

1. 封装思路

  1. 创建一个 modal()wx.showModal() 进行封装
  2. 调用该方法时, 传递对象作为参数, 对象的参数与 wx.showModal() 保持一致
  3. 在需要显示提示框的时候, 直接调用 modal(), 并传入相关参数
    1. 不传递参数, 使用默认值
    2. 传入部分参数, 覆盖默认参数
  4. 封装后的提示框有两种调用方式
    1. 模块化的导入使用
    2. 将对象挂载到 全局 wx 示例对象身上, 可以通过 wx.toast() 来调用提示框

2. 开始封装

miniprogram/utils/extendAPI.js

/**
 * @description 模态对话框
 * @param {Object} options  参数与 wx.showModal() 保持一致
 */
const modal = (options = {}) =>{
  return new Promise((resolve) =>{
    // 默认参数
    const defaultOpt = {
      title: "提示",
      content: "您确定执行该操作吗?",
      confirmColor: '#f3514f'
    }
    // 通过 Object.assgin 来合并对象
    // 第一个是空对象,是为了不破坏 defaultOpt 这个默认参数
    const opts = Object.assign({},defaultOpt,options)

    wx.showModal({
      ...opts,
      complete: ({confirm,cancel}) => {
        confirm && resolve(true);
        cancel && resolve(false);
      }
    })
  })
}

wx.modal = modal  // 将 modal 挂载到 全局 wx 实例对象身上

export { modal }

调用

import { modal } from '../../utils/extendAPI'

Page({
    // 不传参数, 使用默认值, 并获取用户点击的结果
    const res = await modal()
    
    // 传入部分参数, 覆盖默认值, 并获取用户点击的结果
    const res = await modal({
        content: '是否退出登录',
        showCancel: false    // 关闭取消按钮
    })
})

全局 wx 实例对象调用

Page({
    // 不传参数, 使用默认值, 并获取用户点击的结果
    const res = await wx.modal()
    
    // 传入部分参数, 覆盖默认值, 并获取用户点击的结果
    const res = await wx.modal({
        content: '是否退出登录',
        showCancel: false    // 关闭取消按钮
    })
})

3. 本地存储

在小程序开发中, 经常需要将一些数据存储在本地, 方便多个页面的读取使用, 例如: 用户的登录状态、用户的个人信息等小程序提供了同步、异步两类 API 用来实现本地存储操作

1. 同步存储

miniprogram/utils/storage.js

/**
 * @description 将数据存储在本地
 * @param {*} key 指定的key
 * @param {*} value 需要存储的数据
 */
const setStorage = (key, data) => {
  try {
    wx.setStorageSync(key, data)
  } catch (error) {
    console.error(`存储 ${key} 时,发生了异常。`, error)
  }
}

/**
 * @description 读取本地的key
 * @param {*} key 指定的key
 */
const getStorage = (key) => {
  try {
    const value = wx.getStorageSync(key)
    if (value) {
      return value
    }
  } catch (error) {
    console.error(`读取 ${key} 时,发生了异常。`, error)
  }
}

/**
 * @description 移除本地的key
 * @param {*} key 指定的key
 */
const removeStorage = (key) => {
  try {
    wx.removeStorageSync(key)
  } catch (error) {
    console.error(`移除 ${key} 时,发生了异常。`, error)
  }
}


/**
 * @description 清除本地的所有key
 */
const clearStorage = () => {
  try {
    wx.clearStorageSync()
  } catch (error) {
    console.error(`清除本地全部数据时,发生了异常。`, error)
  }
}

export {
  setStorage,
  getStorage,
  removeStorage,
  clearStorage,
}

调用

import {
  setStorage,
  getStorage,
  removeStorage,
  clearStorage
} from '../../utils/storage'

Page({
    
  handler() {
    
    setStorage('num', 1)

    const num = getStorage('num')
    console.log(num);

    removeStorage('num')

    clearStorage()
  }
})

2. 异步存储

miniprogram/utils/storage.js

/**
 * @description 异步将数据存储到本地
 * @param {*} key 指定的key
 * @param {*} data 需要存储的数据
 */
const asyncSetStorage = (key, data) => {
  return new Promise((resolve) => {
    wx.setStorage({
      key,
      data,
      complete: (res) => {
        resolve(res)
      }
    })
  })
}

/**
 * @description 异步读取本地数据
 * @param {*} key 指定的key
 */
const asyncGetStorage = (key) => {
  return new Promise((resolve) => {
    wx.getStorage({
      key,
      complete: (res) => {
        resolve(res)
      }
    })
  })
}

/**
 * @description 异步移除本地数据
 * @param {*} key 指定的key
 */
const asyncRemoveStorage = (key) => {
  return new Promise((resolve) => {
    wx.removeStorage({
      key,
      complete: (res) => {
        resolve(res)
      }
    })
  })
}

/**
 * @description 异步清空本地数据
 */
const asyncClearStorage = () => {
  return new Promise((resolve) => {
    wx.clearStorage({
      complete: (res) => {
        resolve(res)
      }
    })
  })
}

export {
  asyncSetStorage,
  asyncGetStorage,
  asyncRemoveStorage,
  asyncClearStorage
}

调用

import {
  asyncSetStorage,
  asyncGetStorage,
  asyncRemoveStorage,
  asyncClearStorage
} from '../../utils/storage'
Page({
    
  async handler() {
      
    const res = await asyncSetStorage('num', 1)
    console.log(res);

    const num = await asyncGetStorage('num')
    console.log(num);

    const res = await asyncRemoveStorage('num')
    console.log(res);

    const res = await asyncClearStorage()
    console.log(res);
  }
})

4. 网络请求

0. 为什么封装网络请求

小程序大多数 API 都是异步 API, 如: wx.request()wx.login() 等。这类 API 通常接收一个 Object 对象类型参数, 参数中按需要指定以下字段接收接口调用结果


参数名 类型 必填 说明
success function 调用成功的回调函数
fail function 调用失败的回调函数
complete function 调用结束的回调函数 (调用成功、失败都会执行)

如果采用这种回调函数的方法接收返回的值, 如果一个请求是依靠另一个请求的结果来发送请求的话,就会出现 多层success 套用的情况, 容易出现 回调地狱问题

为了解决这个问题, 小程序基础库从 2.10.2 版本起, 异步 API 支持 callback & promise 两种调用方式。当接口参数 Object 对象中不包含 successfailcomplete时, 将默认返回 promise, 否则按照回调方式执行,无返回值

但是部分接口, 如: downloadFilerequestuploadFile 等本身就有返回值, 因此不支持 promise 调用方式, 它们的 promisify 需要开发者自行封装。

Axios 是日常开发中常用的一个基于 promise 的网络请求库, 可以参考 Axios 的 [使用方式] 来封装自己的网络请求模块

1. 封装 request 方法

1. 封装思路

  1. 在封装网络请求模块的Hi好, 采用 Class 类来进行封装, 这样代码更具可复用性, 也方便添加新的方法和属性,提高代码的扩展性
  2. WxRequest 类内部封装一个 request 实例方法,
    1. request 实例方法中需要使用 Promise 封装 wx.request(), 也就是使用 Promise 处理 wx.request 的返回结果。
    2. request 实例方法接收一个 options 对象作为形参, options 参数和调用 wx.request() 时传递的请求配置项一致
    3. 接口调用成功时, 通过 resolve 返回响应数据
    4. 接口调用失败时, 通过 reject 返回错误原因

2. 开始封装

miniprogram/utils/request.js

export class WxRequest {

  constructor() {}

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          resolve(res)
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          reject(err)
        }
      })
    })
  }

}

export default WxRequest

miniprogram/utils/http.js

import WxRequest from './request'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

export default wxr

pages/test/test.js

import wxr from '../../utils/http'

Page({
    
  handler() {

    // .then 方式调用
    wxr.request({
      url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',
      method: 'GET'
    }).then(res => {
      console.log(res)
    }).catch(err => {
      console.log(error)
    })
  }

  async handler2() {

    // async 和 await 方式调用
    try {
      const res = await wxr.request({
        url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',
        method: 'GET'
      })
      console.log(res)
    } catch (error) {
      console.log(error)
    }
  }
})

2. 设置请求参数

1. 三种参数设置

  1. 在发起网络请求时, 通常需要配置一些请求参数。其中一些参数可以设置为默认参数。如: 请求方法超时时长
  2. 但是不同项目的请求参数设置是不同个, 还需要允许在进行实例化的时候, 传入参数, 对默认参数进行修改
  3. 在通过实例调用 request 实例方法时也会传入相关的请求参数

2. 封装思路

  1. 默认参数:WxRequest 类中添加 defaults 实例属性来设置默认值
  2. 实例化时参数: 在对 WxRequest 类进行实例化时传入相关参数, 需要在 constructor 构造函数的形参进行接收
  3. 调用时参数: 调用 request 实例方法时传入请求参数
  4. 默认参数和自定义参数的合并操作, 通常会在 constructor 中进行

3. 开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {
    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          resolve(res)
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          reject(err)
        }
      })
    })
  }
}

export default WxRequest

miniprogram/utils/http.js

import WxRequest from './request'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

export default wxr

pages/test/test.js

import wxr from '../../utils/http'

Page({
  handler() {

    // .then 方式调用
    wxr.request({
      url: '/index/findBanner',
      method: 'GET'
    }).then(res => {
      console.log(res)
    }).catch(err => {
      console.log(error)
    })
  }

  async handler2() {

    // async 和 await 方式调用
    try {
      const res = await wxr.request({
        url: '/index/findBanner',
        method: 'GET'
      })
      console.log(res)
    } catch (error) {
      console.log(error)
    }
  }
})

3. 快速请求

封装四个快捷方法, getpostputdelete 用来快速发送对应请求方式的请求

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {
    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          resolve(res)
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          reject(err)
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }
}

export default WxRequest

pages/test/test.js

import wxr from '../../utils/http'

Page({
  handler() {

    // .then 方式调用
    wxr.get('/index/findBanner').then(res => {
      console.log(res)
    }).catch(err => {
      console.log(error)
    })
  }

  async handler2() {

    // async 和 await 方式调用
    try {
      const res = await wxr.get('/index/findBanner')
      console.log(res)
    } catch (error) {
      console.log(error)
    }
  }
})

4. wx.request 注意事项

  1. 只要成功接收到服务器的返回结果, 无论 statusCode 状态码是多少, 都会执行 success 回调函数
  2. 网络异常请求超时时, 才会执行 fail 回调函数

5. 封装 请求 & 响应 拦截器

为了方便统一处理请求参数以及服务器相应结果, 为 WxRequest 添加拦截器功能

  1. 请求拦截器: 在请求之前调用的函数, 用来对请求参数进行新增和修改
  2. 响应拦截器: 在响应之后调用的函数, 用来响应数据进行额外操作

在发送请求时, 还需要区分是否通过实例调用了拦截器

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

1. 封装思路

  1. WxRequest 类内部定义 interceptors 实例属性, 属性中包含 requestresponse 方法
  2. 是否通过实例调用拦截器
    1. 否: 定义默认拦截器
    2. 是: 实例调用的拦截器覆盖默认拦截器
  3. 在发送请求之前, 调用请求拦截器
  4. 在服务器响应以后, 调用响应拦截器, 不管成功、失败响应, 都需要调用响应拦截器

2. 开始封装

mimiprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {
    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, res, { config: options })
          resolve(this.interceptors.response(resObj))
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, err, { config: options })
          reject(this.interceptors.response(resObj))
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }
}

export default WxRequest

miniprogram/utils/http.js

import WxRequest from './request'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

// 配置请求拦截器
wxr.interceptors.request = (config) => {
  // 在发送请求前, 发送token
  config.token = token
  return config
}

// 配置响应拦截器
wxr.interceptors.response = (response) => {
  const { data } = response
  return data   // 将响应处理后返回
}

export default wxr

6. 完善 请求 & 响应拦截器

在响应拦截器, 需要判断是请求成功, 还是请求失败, 然后进行不同的业务逻辑处理, 例如: 请求成功以后将数据简化返回, 网络出现异常则给用户进行网络异常提示

1. 封装思路

  1. 如果请求成功, 将响应成功的数据传递给响应拦截器, 同时在传递的数据中新增 isSuccess: true 字段, 表示请求成功
  2. 如果请求失败, 将响应失败的数据传递给响应拦截器, 同时在传递的数据中新增 isSuccess: false字段, 表示请求失败
  3. 在实例调用响应拦截器时, 如果 isSuccess: true, 则表示服务器成功响应了结果, 就可以将数据简化以后返回
  4. 在实例调用响应拦截器时, 如果 isSuccess: false, 则表示网络超时或其他网络问题, 提示 网络异常, 同时返回即可

2. 开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {
    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, res, {
            config: options,
            isSuccess: true
          })
          resolve(this.interceptors.response(resObj))
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, err, {
            config: options,
            isSuccess: false
          })
          reject(this.interceptors.response(resObj))
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }
}

export default WxRequest

miniprogram/utils/http.js

import WxRequest from './request'
import {
  getStorage,
  clearStorage
} from './storage'
import {modal, toast} from './extendAPI'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

// 配置请求拦截器
wxr.interceptors.request = (config) => {
  // 在发送请求前, 需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  //  如果存在,则需要在请求头中添加该令牌
  if (token) {
    config.header.token = token
  }
  return config
}

// 配置响应拦截器
wxr.interceptors.response = async (response) => {
  const {
    isSuccess,
    data
  } = response
  // 请求失败
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }
  
  // 请求成功
  return data
}

export default wxr

7. 使用 请求 & 响应 拦截器

1. 思路分析

  1. 在发送请求时, 购物车列表、收货地址、更新头像等接口, 都需要进行权限验证, 因此需要在请求拦截器中判断本地是否存在访问令牌 token , 如果存在就需要在请求头中添加 token 字段
  2. 在使用 wx.request 发送网络请求时, 只要成功接收到服务器返回结果, 无论 statusCode 是多少, 都会进入 success 回调, 因此需要对业务状态码进行逻辑判断
    1. 业务状态码 === 200, 说明接口请求成功, 服务器成功返回了数据
    2. 业务状态码 === 208, 说明没有 token 或者 token 过期失效, 需要登录或重新登录获取最新 token
    3. 业务状态码 === 其他, 说明请求或响应出现了异常

2. 开始封装

miniprogram/utils/http.js

import WxRequest from './request'
import {
  getStorage,
  clearStorage
} from './storage'
import {modal, toast} from './extendAPI'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

// 配置请求拦截器
wxr.interceptors.request = (config) => {
  // 在发送请求前, 需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  //  如果存在,则需要在请求头中添加该令牌
  if (token) {
    config.header.token = token
  }
  return config
}

wxr.interceptors.response = async (response) => {
  const {
    isSuccess,
    data
  } = response
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }

  // 判断状态码
  switch (data.code) {
    // 如果状态码为200, 返回简化后的数据
    case 200:
      return data
    // 没有 token 或 token 过期, 则需要 登录 或 重新登录
    case 208:   
      const res = await modal({
        content: '鉴权失败, 请重新登录',
        showCancel: false    // 不显示取消按钮
      })
      if (res) {  // 用户点击了确定
        // 移除失效 token, 同时清除本地缓存的所有数据
        clearStorage()

        // 跳转到登录页面
        wx.navigateTo({
          url: '/pages/login/login',
        })
      }
      return Promise.reject(response)
    default:
      toast({
        title: "程序出现异常, 请联系客服或稍后重试"
      })
      return Promise.reject(response)
  }
}

export default wxr

8. 添加并发请求

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

1. 使用 asyncawait 方式, 同时发送多个请求

import wxr from '../../utils/http'

Page({
  async handler() {
    // 一个请求有了响应结果之后再发送下一个请求
    // 所有的请求事件之和, 就是整个操作的执行时间
    const res = await wxr.get('/index/findBanner')
    const res2 = await wxr.get('/index/findCategory1')
    const res3 = await wxr.get('/index/findBanner')

  }

})

2. 使用 Promise.all() 方式 , 同时发送多个请求

import wxr from '../../utils/http'

Page({
  async handler() {
    // 并行的发送请求, 此时最长的一次请求时间, 就是整个操作的请求时间
    const res = await Promise.all([wxr.get('/index/findBanner'),wxr.get('/index/findCategory1'),wxr.get('/index/findBanner')])
  }

})

3. 开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {
    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, res, {
            config: options,
            isSuccess: true
          })
          resolve(this.interceptors.response(resObj))
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, err, {
            config: options,
            isSuccess: false
          })
          reject(this.interceptors.response(resObj))
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }

  /**
   * @description  处理并发请求
   * @param  {...any} promise // 通过展开运算符来接收参数, 会将参数聚合成数组
   */
  all(...promise){   
    return Promise.all(promise)
  }
}

export default WxRequest

9. 添加 loading

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

  1. 在发送请求之前, 需要通过 wx.showLoading 展示 loading 效果
  2. 在服务器响应数据以后, 需要通过 wx.hideLoading 隐藏 loading 效果

2. 要不要加 loadingWxRequest

  1. 在类内部进行添加, 方便多个项目直接使用类提供的 loading 效果, 也方便统一优化 wx.showLoading 使用体验。但是不方便自己进行 loading 的个性化定制
  2. 如果想自己来控制 loading 效果, 带来更丰富的交互体验, 就不需要将 loading 封装到类内部, 但是需要开发者自己来优化 wx.showLoading 使用体验, 每个项目都要写一份

3. 开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {
    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

    // 请求发送前, 展示 loading 效果
    wx.showLoading({ title: '加载中...' })

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, res, {
            config: options,
            isSuccess: true
          })
          resolve(this.interceptors.response(resObj))
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, err, {
            config: options,
            isSuccess: false
          })
          reject(this.interceptors.response(resObj))
        },

        // 接口调用结束的回调函数(调用成功、失败都会执行)
        complete: () => {
          // 不管请求成功还是失败, 都要隐藏 loading
          wx.hideLoading()
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }

  /**
   * @description  处理并发请求
   * @param  {...any} promise // 通过展开运算符来接收参数, 会将参数聚合成数组
   */
  all(...promise){   
    return Promise.all(promise)
  }
}

export default WxRequest

10. 完善 loading

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

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

2. 可以通过 队列 的方式解决上面的三个问题

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

3. 开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000 // 请求超时时间, 默认为一分钟
  }

  // 存放请求标识, 用来存放请求标识, 作为显示 loading 的判断依据
  queue = []

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {

    // 如果有新的请求, 就先清除上一次请求的定时器
    this.timerID && clearTimeout(this.timerID)

    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

    // 请求发送前,判断队列是否为空,如果是空数组则,展示 loading 效果
    this.queue.length === 0 && wx.showLoading({ title: '加载中...' })

    // 每次发起请求, 往队列中添加一个请求标识
    this.queue.push('request')


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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, res, {
            config: options,
            isSuccess: true
          })
          resolve(this.interceptors.response(resObj))
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, err, {
            config: options,
            isSuccess: false
          })
          reject(this.interceptors.response(resObj))
        },

        // 接口调用结束的回调函数(调用成功、失败都会执行)
        complete: () => {
          // 不管请求成功还是失败, 都要隐藏 loading
          this.queue.pop('request')

          this.queue.length === 0 && this.queue.push('request')

          this.timerID = setTimeout(() => {
            // 移除
            this.queue.pop('request')
            // 判断请求队列是否为空,如果为空,则表示并发请求完成了, 则隐藏 loading
            this.queue.length === 0 && wx.hideLoading()
            clearTimeout(this.timerID)
          }, 1)
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }

  /**
   * @description  处理并发请求
   * @param  {...any} promise // 通过展开运算符来接收参数, 会将参数聚合成数组
   */
  all(...promise) {
    return Promise.all(promise)
  }
}

export default WxRequest

11. 控制 loading 显示

上面代码中, 通过 wx.showLoading() 默认显示了 loading 效果, 但是在实际开发中, 有的接口可能不需要显示 loading 效果, 或者开发者希望自己来控制 loading 的样式和交互, 那么需要关闭这个默认 loading 效果

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

开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000, // 请求超时时间, 默认为一分钟
    isShowLoading: true // 控制是否使用默认的 loading 效果
  }

  // 存放请求标识, 用来存放请求标识, 作为显示 loading 的判断依据
  queue = []

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {

    // 如果有新的请求, 就先清除上一次请求的定时器
    this.timerID && clearTimeout(this.timerID)

    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

    if (options.isShowLoading) {
      // 请求发送前,判断队列是否为空,如果是空数组则,展示 loading 效果
      this.queue.length === 0 && wx.showLoading({ title: '加载中...' })

      // 每次发起请求, 往队列中添加一个请求标识
      this.queue.push('request')
    }

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,

        // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
        success: (res) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, res, {
            config: options,
            isSuccess: true
          })
          resolve(this.interceptors.response(resObj))
        },

        // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
        fail: (err) => {
          // 不管是成功响应还是失败相应, 都需要调用响应拦截器
          const resObj = Object.assign({}, err, {
            config: options,
            isSuccess: false
          })
          reject(this.interceptors.response(resObj))
        },

        // 接口调用结束的回调函数(调用成功、失败都会执行)
        complete: () => {
          if (options.isShowLoading) {
            // 不管请求成功还是失败, 都要隐藏 loading
            this.queue.pop('request')

            this.queue.length === 0 && this.queue.push('request')

            this.timerID = setTimeout(() => {
              // 移除
              this.queue.pop('request')
              // 判断请求队列是否为空,如果为空,则表示并发请求完成了, 则隐藏 loading
              this.queue.length === 0 && wx.hideLoading()
              clearTimeout(this.timerID)
            }, 1)
          }
        }
      })
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }

  /**
   * @description  处理并发请求
   * @param  {...any} promise // 通过展开运算符来接收参数, 会将参数聚合成数组
   */
  all(...promise) {
    return Promise.all(promise)
  }
}

export default WxRequest

pages/test/test.js 某个接口不想显示 loading

import wxr from '../../utils/http'
Page({
  async handler() {
    // 在第三个参数, 传递配置项 isShowLoading: false
    wxr.get("/index/findBanner", null, { isShowLoading: false }).then((res) => {
        
    })
  }
})

miniprogram/utils/http.js 整个项目不想显示 loading

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000,
  isShowLoading: false     // 实例化的时候,传入配置参数 isShowLoading: false  
})

pages/test/test.js 某个接口想显示 loading

import wxr from '../../utils/http'
Page({
  async handler() {
    // 在第三个参数, 传递配置项 isShowLoading: true
    wxr.get("/index/findBanner", null, { isShowLoading: true }).then((res) => {
        
    })
  }
})

5. 文件上传

1. wx.uploadFile() 用于将本地资源上传到服务器, 例如: 在获取到微信头像后, 将微信头像上传到公司服务器

wx.uploadFile({
    url: '',         // 必填项, 开发者服务器地址
    filePath: '',    // 必填项, 要上传的文件资源路径 (本地路径)
    name: ''         // 必填项, 文件对应的key, 开发者在服务端可以通过这个 key 获取文件的二进制内容
    success: (res) => {   // 请求成功的回调函数
    
	}
})

2. 开始封装

miniprogram/utils/request.js

export class WxRequest {
  // 默认参数对象
  defaults = {
    baseURL: '', // 发送请求的基准地址
    url: '', // 请求的接口路径
    data: null, // 请求参数
    method: "GET", // 默认请求方法
    header: { //  请求头
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000, // 请求超时时间, 默认为一分钟
    isShowLoading: true // 控制是否使用默认的 loading 效果
  }

  // 存放请求标识, 用来存放请求标识, 作为显示 loading 的判断依据
  queue = []

  // 定义拦截器对象, 需要包含请求拦截器和响应拦截器
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  /**
   * @description 定义 constructor 构造函数, 用于创建和初始化类的属性和方法
   * @param {Object} params 实例化时,传入的请求配置项
   */
  constructor(params = {}) { // 在实例化时,传递的参数会被 params 接收到
    // 调用 Object.assgin() ,用来将实例化时传递的参数合并覆盖默认参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  /**
   * @description 发送网络请求
   * @param {Object} options 传入的请求参数
   */
  request(options) {

    // 如果有新的请求, 就先清除上一次请求的定时器
    this.timerID && clearTimeout(this.timerID)

    // 拼接完整的请求地址
    options.url = this.defaults.baseURL + options.url

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

    if (options.isShowLoading && options.method !== 'UPLOAD') {
      // 请求发送前,判断队列是否为空,如果是空数组则,展示 loading 效果
      this.queue.length === 0 && wx.showLoading({ title: '加载中...' })

      // 每次发起请求, 往队列中添加一个请求标识
      this.queue.push('request')
    }

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

    // 使用 Promise 封装 wx.request, 处理异步请求
    return new Promise((resolve, reject) => {
      if (options.method === 'UPLOAD') {
        wx.uploadFile({
          ...options,

          success: (res) => {
            // 需要将服务器返回的 JSON 字符串 转成对象
            res.data = JSON.parse(res.data)

            // 合并参数
            const resObj = Object.assign({}, res, {
              config: options,
              isSuccess: true
            })
            resolve(this.interceptors.response(resObj))
          },

          fail: (err) => {
            // 合并参数
            const resErr = Object.assign({}, err, {
              config: options,
              isSuccess: false
            })
            reject(this.interceptors.response(resErr))
          }
        })
      } else {
        wx.request({
          ...options,

          // 当接口调用成功时,触发 success 回调函数,这时候需要将响应结果返回
          success: (res) => {
            // 不管是成功响应还是失败相应, 都需要调用响应拦截器
            const resObj = Object.assign({}, res, {
              config: options,
              isSuccess: true
            })
            resolve(this.interceptors.response(resObj))
          },

          // 当接口调用失败时,触发 fail 回调函数,这时候需要将错误结果返回
          fail: (err) => {
            // 不管是成功响应还是失败相应, 都需要调用响应拦截器
            const resErr = Object.assign({}, err, {
              config: options,
              isSuccess: false
            })
            reject(this.interceptors.response(resErr))
          },

          // 接口调用结束的回调函数(调用成功、失败都会执行)
          complete: () => {
            if (options.isShowLoading) {
              // 不管请求成功还是失败, 都要隐藏 loading
              this.queue.pop('request')

              this.queue.length === 0 && this.queue.push('request')

              this.timerID = setTimeout(() => {
                // 移除
                this.queue.pop('request')
                // 判断请求队列是否为空,如果为空,则表示并发请求完成了, 则隐藏 loading
                this.queue.length === 0 && wx.hideLoading()
                clearTimeout(this.timerID)
              }, 1)
            }
          }
        })
      }
    })
  }

  /** 
   * @description 快速发送 GET 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * @description 快速发送 POST 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'POST'
    }, config))
  }

  /**
   * @description 快速发送 PUT 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'PUT'
    }, config))
  }

  /**
   * @description 快速发送 DELETE 请求
   * @param {String} url 请求的接口路径
   * @param {Object} data 请求参数
   * @param {Object} config 请求配置信息
   */
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({
      url,
      data,
      method: 'DELETE'
    }, config))
  }

  /**
   * @description  处理并发请求
   * @param  {...any} promise // 通过展开运算符来接收参数, 会将参数聚合成数组
   */
  all(...promise) {
    return Promise.all(promise)
  }

  /**
   * @description  文件上传接口
   * @param {String} url   服务器地址
   * @param {String} filePath  文件资源地址
   * @param {String} name  文件对应的 key
   * @param {Object} config 其他配置项
   */
  upload(url, filePath, name = 'file', config = {}) {
    return this.request(Object.assign({
      url,
      filePath,
      name,
      method: 'UPLOAD'
    }, config))
  }
}

export default WxRequest

pages/test/test.wxml

<view class="avatar">
  <button open-type="chooseAvatar" bindchooseavatar="getAvatar">
    <image src="{{ avatar }}" mode="" />
  </button>
</view>

page/test/test.scss

.avatar {
  button {
    background-color: #fff;
    image {
      height: 240rpx;
      width: 240rpx;
      border: 1rpx solid red;
      border-radius: 50%;
    }
  }
}

pages/test/test.js

import wxr from '../../utils/http'
Page({
  data: {
    avatar: ''
  },
  async getAvatar(event) {
    const avatarUrl = event.detail.avatarUrl

    // 发送请求, 将头像保存在服务器
    const { data:avatar } = await wxr.upload('/fileUpload',avatarUrl)
    this.setData({
      avatar: avatar
    })
  }
})

6. mina-request

0. 官方文档

https://www.npmjs.com/package/mina-request

1. 安装

npm i mina-request

2. 工具 ==> 构建Npm

3. 如果使用上面步骤封装了 WxRequest, 则只需要修改 miniprogram/utils/http.js

import WxRequest from 'mina-request'

4. 如果没有封装 WxRequest, miniprogram/utils/http.js

import WxRequest from 'mina-request'
import {
  getStorage,
  clearStorage
} from './storage'
import {modal, toast} from './extendAPI'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

// 配置请求拦截器
wxr.interceptors.request = (config) => {
  // 在发送请求前, 需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  //  如果存在,则需要在请求头中添加该令牌
  if (token) {
    config.header.token = token
  }
  return config
}

wxr.interceptors.response = async (response) => {
  const {
    isSuccess,
    data
  } = response
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }

  // 判断状态码
  switch (data.code) {
    // 如果状态码为200, 返回简化后的数据
    case 200:
      return data
    // 没有 token 或 token 过期, 则需要 登录 或 重新登录
    case 208:   
      const res = await modal({
        content: '鉴权失败, 请重新登录',
        showCancel: false    // 不显示取消按钮
      })
      if (res) {  // 用户点击了确定
        // 移除失效 token, 同时清除本地缓存的所有数据
        clearStorage()

        // 跳转到登录页面
        wx.navigateTo({
          url: '/pages/login/login',
        })
      }
      return Promise.reject(response)
    default:
      toast({
        title: "程序出现异常, 请联系客服或稍后重试"
      })
      return Promise.reject(response)
  }
}

export default wxr
posted @ 2024-06-20 15:21  河图s  阅读(51)  评论(0)    收藏  举报