微信小程序 --- wx.request网络请求封装

小程序 0

网络请求封装

网络请求模块难度较大,如果学习起来感觉吃力,可以直接学习 [请求封装-使用 npm 包发送请求] 以后的模块

01. 为什么要封装 wx.request

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

参数名类型必填说明
successfunction调用成功的回调函数
failfunction调用失败的回调函数
completefunction调用结束的回调函数(调用成功、失败都会执行)
wx.request({  // 接口调用成功的回调函数  success() {    wx.request({      success() {        wx.request({          success() {            wx.request({              success() {                wx.request({                  success() {                    wx.request({                      success() {                        wx.request({                          success() {                            wx.request({                              success() {}                            })                          }                        })                      }                    })                  }                })              }            })          }        })      }    })  },  // 接口调用失败的回调函数  fail() {},  // 接口调用结束的回调函数(调用成功、失败都会执行)  complete() {}})

如果采用这种回调函数的方法接收返回的值,可能会出现多层 success 套用的情况,容易出现回调地狱问题,

为了解决这个问题,小程序基础库从 2.10.2 版本起,异步 API 支持 callback & promise 两种调用方式。

当接口参数 Object 对象中不包含 success/fail/complete 时,将默认返回 promise,否则仍按回调方式执行,无返回值。

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

Axios 是我们日常开发中常用的一个基于 promise 的网络请求库

我们可以参考 Axios 的 [使用方式] 来封装自己的网络请求模块,咱们看一下使用的方式:

网络请求模块封装

import WxRequest from 'mina-request'// 自定义配置新建一个实例const instance = new WxRequest(({  baseURL: 'https://some-domain.com/api/',  timeout: 1000,  headers: {'X-Custom-Header': 'foobar'}})// 通过 instance.request(config) 方式发起网络请求instance.requst({  method: 'post',  url: '/user/12345',  data: {    firstName: 'Fred',    lastName: 'Flintstone'  }})// 通过 instance.get 方式发起网络请求instance.get(url, data, config)// 通过 instance.delete 方式发起网络请求instance.delete(url, data, config)// 通过 instance.post 方式发起网络请求instance.post(url, data, config)// 通过 instance.put 方式发起网络请求instance.put(url, data, config)// ----------------------------------------------// 添加请求拦截器instance.interceptors.request = (config) => {  // 在发送请求之前做些什么  return config}// 添加响应拦截器instance.interceptors.response = (response) => {  // response.isSuccess = true,代码执行了 wx.request 的 success 回调函数  // response.isSuccess = false,代码执行了 wx.request 的 fail 回调函数      // response.statusCode // http 响应状态码      // response.config // 网络请求请求参数      // response.data 服务器响应的真正数据      // 对响应数据做点什么  return response}

封装后网络请求模块包含以下功能

  1. 包含 request 实例方法发送请求
  2. 包含 get、delete、put、post 等实例方法可以快捷的发送网络请求
  3. 包含 请求拦截器、响应拦截器
  4. 包含 uploadFile 将本地资源上传到服务器 API
  5. 包含 all 并发请求方法
  6. 同时优化了并发请求时 loading 显示效果

02. 请求封装-request 方法

思路分析:

在封装网络请求模块的时候,采用 Class 类来进行封装,采用类的方式封装代码更具可复用性,也方便地添加新的方法和属性,提高代码的扩展性

我们先创建一个 class 类,同时定义 constructor 构造函数

// 创建 WxRequest 类class WxRequest {  constructor() {}}

我们在 WxRequest 类内部封装一个 request 实例方法

request 实例方法中需要使用 Promise 封装 wx.request,也就是使用 Promise 处理 wx.request 的返回结果

request 实例方法接收一个 options 对象作为形参,options 参数和调用 wx.request 时传递的请求配置项一致

  • 接口调用成功时,通过 resolve 返回响应数据
  • 接口调用失败时,通过 reject 返回错误原因
class WxRequest {  // 定义 constructor 构造函数,用于创建和初始化类的属性和方法  constructor() {}  /**   * @description 发起请求的方法   * @param { Object} options 请求配置选项,同 wx.request 请求配置选项   * @returns Promise   */  request(options) {    // 使用 Promise 封装异步请求    return new Promise((resolve, reject) => {      // 使用 wx.request 发起请求      wx.request({        ...options,        // 接口调用成功的回调函数        success: (res) => {          resolve(res)        },        // 接口调用失败的回调函数        fail: (err) =>  {          reject(err)        }      })    })  }}

然后对 WxRequest 进行实例化,然后测试 request 实例方法是否封装成功!

注意:我们先将类 和 实例化的对象放到同一个文件中,这样方便进行调试,后面我们在拆分成两个文件

class WxRequest {  // coding....}// ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化const instance = new WxRequest()// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

在其他模块中引入封装的文件后,我们期待通过 request() 方式发起请求,以 promise 的方式返回参数

// 导入创建的实例import instance from '../../utils/wx-request'Page({      // 点击按钮触发 handler 方法  async handler() {    // 通过实例调用 request 方法发送请求    const res = await instance.request({      url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',      method: 'GET'    })    console.log(res)  }})

落地代码:

➡️ /utils/request.js

// 创建 WxRequest 类,采用类的方式进行封装会让方法更具有复用性,也可以方便进行添加新的属性和方法class WxRequest {  // 定义 constructor 构造函数,用于创建和初始化类的属性和方法  constructor() {}  /**   * @description 发起请求的方法   * @param { Object} options 请求配置选项,同 wx.request 请求配置选项   * @returns Promise   */  request(options) {    // 使用 Promise 封装异步请求    return new Promise((resolve, reject) => {      // 使用 wx.request 发起请求      wx.request({        ...options,        // 接口调用成功的回调函数        success: (res) => {          resolve(res)        },        // 接口调用失败的回调函数        fail: (err) => {          reject(err)        }      })    })  }}// ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化const instance = new WxRequest()// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

➡️ /pages/test/test.js

import instance from '../../utils/request'Page({      // 点击按钮触发 handler 方法  async handler() {    // 第一种调用方式:通过 then 和 catch 接收返回的值    // instance    //   .request({    //     url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',    //     method: 'GET'    //   })    //   .then((res) => {    //     console.log(res)    //   })    //   .catch((err) => {    //     console.log(err)    //   })    // 第二种调用方式:通过 await 和 async 接收返回的值	const res = await instance.request({      url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',      method: 'GET'    })    console.log(res)  }})

03. 请求封装-设置请求参数

思路分析:

在发起网络请求时,需要配置一些请求参数,

其中有一些参数我们可以设置为默认参数,例如:请求方法、超时时长 等等,因此我们在封装时我们要定义一些默认的参数。

// 默认参数对象defaults = {  baseURL: '', // 请求基准地址  url: '', // 开发者服务器接口地址  data: null, // 请求参数  method: 'GET',// 默认请求方法  // 请求头  header: {    'Content-type': 'application/json' // 设置数据的交互格式  },  timeout: 60000 // 小程序默认超时时间是 60000,一分钟  // 其他参数...}

但是不同的项目,请求参数的设置是不同的,我们还需要允许在进行实例化的时候,传入参数,对默认的参数进行修改。例如:

// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api', // 请求基准地址  timeout: 10000 // 微信小程序 timeout 默认值为 60000})

在通过实例,调用 request 实例方法时也会传入相关的请求参数

const res = await instance.request({  url: '/index/findBanner',  method: 'GET'})

从而得出结论:请求参数的设置有三种方式:

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

默认参数和自定义参数的合并操作,通常会在constructor中进行。

因此我们就在 constructor 中将开发者传入的相关参数和defaults 默认值进行合并,需要传入的配置项覆盖默认配置项

class WxRequest {    +   // 默认参数对象+   defaults = {+     baseURL: '', // 请求基准地址+     url: '', // 开发者服务器接口地址+     data: null, // 请求参数+     method: 'GET',// 默认请求方法+     // 请求头+     header: {+       'Content-type': 'application/json' // 设置数据的交互格式+     },+     timeout: 60000 // 小程序默认超时时间是 60000,一分钟+   }  /**   * @description 定义 constructor 构造函数,用于创建和初始化类的属性和方法   * @param {*} params 用户传入的请求配置项   */+   constructor(params = {}) {+     // 在实例化时传入的参数能够被 constructor 进行接收+     console.log(params)    +     // 使用 Object.assign 合并默认参数以及传递的请求参数+     this.defaults = Object.assign({}, this.defaults, params)+   }   // coding....}// ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化+ const instance = new WxRequest({+   baseURL: 'https://gmall-prod.atguigu.cn/mall-api',+   timeout: 15000+ })// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

在调用 request 实例时也会传入相关的参数,是发起请求真正的参数,

我们需要将调用 reqeust 实例方法时传入的参数,继续覆盖合并以后的参数,请求才能够发送成功

注意:让使用传入的参数覆盖默认的参数,同时拼接完整的请求地址。

// 创建 request 请求方法request(options) {+  // 拼接完整的请求地址+  options.url = this.defaults.baseURL + options.url+  // 合并请求参数+  options = { ...this.defaults, ...options }  return new Promise((resolve, reject) => {    // coding...  })}

落地代码:

➡️ utils/request.js

// 创建 Request 类,用于封装 wx.request() 方法class WxRequest {    +   // 默认参数对象+   defaults = {+     baseURL: '', // 请求基准地址+     url: '', // 开发者服务器接口地址+     data: null, // 请求参数+     method: 'GET',// 默认请求方法+     // 请求头+     header: {+       'Content-type': 'application/json' // 设置数据的交互格式+     },+     timeout: 60000 // 小程序默认超时时间是 60000,一分钟+   }    +   /**+    * @description 定义 constructor 构造函数,用于创建和初始化类的属性和方法+    * @param {*} params 用户传入的请求配置项+    */+   constructor(params = {}) {+     // 在实例化时传入的参数能够被 constructor 进行接收+     console.log(params)    +     // 使用 Object.assign 合并默认参数以及传递的请求参数+     this.defaults = Object.assign({}, this.defaults, params)+   }  /**   * @description 发起请求的方法   * @param { Object} options 请求配置选项,同 wx.request 请求配置选项   * @returns Promise   */  request(options) {+    // 拼接完整的请求地址+    options.url = this.defaults.baseURL + options.url+    // 合并请求参数+    options = { ...this.defaults, ...options }    // 方法返回一个 Promise 对象    return new Promise((resolve, reject) => {      // coding...    })  }}// ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',  timeout: 15000})// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

04. 请求封装-封装请求快捷方法

思路分析:

目前已经完成了 request() 请求方法的封装,同时处理了请求参数。

每次发送请求时都使用 request() 方法即可,但是项目中的接口地址有很多,不是很简洁

const res = await instance.request({  url: '/index/findBanner',  method: 'GET'})

所以我们在 request() 基础上封装一些快捷方法,简化 request() 的调用。

需要封装 4 个快捷方法,分别是 getdeletepostput,他们的调用方式如下:

instance.get('请求地址', '请求参数', '请求配置')instance.delete('请求地址', '请求参数', '请求配置')instance.post('请求地址', '请求参数', '请求配置')instance.put('请求地址', '请求参数', '请求配置')

这 4 个请求方法,都是通过实例化的方式进行调用,所以需要 Request 类中暴露出来 getdeletepostput 方法。每个方法接收三个参数,分别是:接口地址、请求参数以及其他参数。

这 4 个快捷方法,本质上其实还是调用 request 方法,我们只要在方法内部组织好参数,调用 request 发送请求即可

class WxRequest {    // coding...+   // 封装 GET 实例方法+   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))+   }}    // ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',  timeout: 15000})// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

落地代码:

➡️ utils/request.js

class WxRequest {    // coding...+   // 封装 GET 实例方法+   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))+   }}    // ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',  timeout: 15000})// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

➡️ /pages/test/test.js

// 导入创建的实例import instance from '../../utils/wx-request'Page({  async handler() {          // 第一种调用方式:通过 then 和 catch 接收返回的值    // instance    //   .request({    //     url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',    //     method: 'GET'    //   })    //   .then((res) => {    //     console.log(res)    //   })    //   .catch((err) => {    //     console.log(err)    //   })    // 第二种调用方式    // 通过实例调用 request 方法发送请求    // const res = await instance.request({    //   url: '/index/findBanner',    //   method: 'GET'    // })    // console.log(res)    // 第三种调用方式:通过调用快捷方式接收返回的值    const res = await instance.get('/index/findBanner')    console.log(res)  }})

05. 请求封装-wx.request 注意事项

知识点:

在使用 wx.request 发送网络请求时。

只要成功接收到服务器返回,无论statusCode是多少,都会进入 success 回调

开发者根据业务逻辑对返回值进行判断。

什么时候会有 fail 回调函数 ?

一般只有网络出现异常、请求超时等时候,才会走 fail 回调

在这里插入图片描述

落地代码:

测试代码

request() {  wx.request({    url: 'https://gmall-prod.atguigu.cn/mall-api/index/findCategory',    method: 'GET',    // timeout: 100, 测试网络超时,需要调整网络    success: (res) => {      console.log('只要成功接收到服务器返回,不管状态是多少,都会进入 success 回调')      console.log(res)    },    fail: (err) => {      console.log(err)    }  })}

06. 请求封装-定义请求/响应拦截器

思路分析:

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

请求拦截器本质上是在请求之前调用的函数,用来对请求参数进行新增和修改

响应拦截器本质上是在响应之后调用的函数,用来对响应数据做点什么

注意:不管成功响应还是失败响应,都会执行响应拦截器

拦截器的使用方式:

// 请求拦截器instance.interceptors.request = (config) => {      // 在发送请求之前做些什么  return config}// 响应拦截器instance.interceptors.response = (response) => {      // 对响应数据做点什么  return response}

通过使用方式,我们可以得出结论:

可以在 WxRequest 类内部定义 interceptors 实例属性,属性中需要包含 request 以及 response 方法

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

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

实现拦截器的思路:

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

WxRequest 类内部定义 interceptors 实例属性,属性中需要包含 request 以及 response 方法。

没有使用拦截器,定义默认拦截器,需要将默认的请求参数进行返回。

如果使用了拦截器,那么使用者的拦截器会覆盖默认的拦截器方法

class WxRequest {      // coding...    +   // 定义拦截器对象,包含请求拦截器和响应拦截器方法,方便在请求或响应之前进行处理。+   interceptors = {+     // 请求拦截器+     request: (config) => config,+     // 响应拦截器+     response: (response) => response+   }  // 用于创建和初始化类的属性以及方法  // 在实例化时传入的参数,会被 constructor 形参进行接收  constructor(options = {}) {    // coding...  }}// ----------------- 以下是实例化的代码 --------------------// 目前写到同一个文件中,是为了方便进行测试,以后会提取成多个文件// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',  timeout: 15000})+ // 配置请求拦截器+ instance.interceptors.request = (config) => {+   // 在发送请求之前做些什么+   return config+ }+ // 响应拦截器+ instance.interceptors.response = (response) => {+   // 对响应数据做点什么+   return response+ }// 将 WxRequest 实例进行暴露出去,方便在其他文件中进行使用export default instance

在发送请求之前,调用请求拦截器,在服务器响应以后,调用响应拦截器

不管成功、失败,都需要调用响应拦截器

class WxRequest {  // coding...      // request 实例方法接收一个对象类型的参数  // 属性值和 wx.request 方法调用时传递的参数保持一致  request(options) {    // 注意:需要先合并完整的请求地址 (baseURL + url)    // https://gmall-prod.atguigu.cn/mall-api/index/findBanner    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 mergetRes = Object.assign({}, res, { config: options })+           resolve(this.interceptors.response(mergetRes))        },        // 当接口调用失败时会触发 fail 回调函数        fail: (err) => {+           // 不管接口成功还是失败,都需要调用响应拦截器+            const mergetErr = Object.assign({}, err, { config: options })+            reject(this.interceptors.response(mergetErr))        }      })    })  }  // coding...}

落地代码:

➡️ utils/request.js

// 创建 WxRequest 类// 通过类的方式来进行封装,会让代码更加具有复用性// 也可以方便添加新的属性和方法class WxRequest {  // 定义实例属性,用来设置默认请求参数  defaults = {    baseURL: '', // 请求基准地址    url: '', // 接口的请求路径    data: null, // 请求参数    method: 'GET', // 默认的请求方法    // 请求头    header: {      'Content-type': 'application/json' // 设置数据的交互格式    },    timeout: 60000 // 默认的超时时长,小程序默认的超时时长是 1 分钟  }+   // 定义拦截器对象,包含请求拦截器和响应拦截器方法,方便在请求或响应之前进行处理。+   interceptors = {+     // 请求拦截器+     request: (config) => config,+     // 响应拦截器+     response: (response) => response+   }  // 用于创建和初始化类的属性以及方法  // 在实例化时传入的参数,会被 constructor 形参进行接收  constructor(params = {}) {    // 通过 Object.assign 方法合并请求参数    // 注意:需要传入的参数,覆盖默认的参数,因此传入的参数需要放到最后    this.defaults = Object.assign({}, this.defaults, params)  }  // request 实例方法接收一个对象类型的参数  // 属性值和 wx.request 方法调用时传递的参数保持一致  request(options) {    // 注意:需要先合并完整的请求地址 (baseURL + url)    // https://gmall-prod.atguigu.cn/mall-api/index/findBanner    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 mergeRes = Object.assign({}, res, { config: options })+           resolve(this.interceptors.response(mergeRes))        },        // 当接口调用失败时会触发 fail 回调函数        fail: (err) => {+           // 不管接口成功还是失败,都需要调用响应拦截器+           const mergeErr = Object.assign({}, err, { iconfig: options })+           // 不管接口成功还是失败,都需要调用响应拦截器+           err = this.interceptors.response(mergeErr)+           reject(err)        }      })    })  }  // 封装 GET 实例方法  get(url, data = {}, config = {}) {    // 需要调用 request 请求方法发送请求,只需要组织好参数,传递给 request 请求方法即可    // 当调用 get 方法时,需要将 request 方法的返回值 return 出去    return this.request(Object.assign({ url, data, method: 'GET' }, config))  }  // 封装 DELETE 实例方法  delete(url, data = {}, config = {}) {    return this.request(Object.assign({ url, data, method: 'DELETE' }, 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))  }}// ----------------- 以下是实例化的代码 --------------------// 目前写到同一个文件中,是为了方便进行测试,以后会提取成多个文件// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',  timeout: 15000})+ // 配置请求拦截器+ instance.interceptors.request = (config) => {+   // 在发送请求之前做些什么+   return config+ }+ // 响应拦截器+ instance.interceptors.response = (response) => {+ +   // 对响应数据做点什么+   return response.data+ }// 将 WxRequest 实例进行暴露出去,方便在其他文件中进行使用export default instance

07. 请求封装-完善请求/响应拦截器

思路分析:

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

例如:请求成功以后将数据简化返回,网络出现异常则给用户进行网络异常提示。

目前不管请求成功 (success),还是请求失败(fail),都会执行响应拦截器

那么怎么判断是请求成功,还是请求失败呢 ?

封装需求:

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

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

  • 如果isSuccess: true 表示服务器响应了结果,我们可以将服务器响应的数据简化以后进行返回
  • 如果isSuccess: false 表示是网络超时或其他网络问题,提示 网络异常,同时将返回即可

落地代码:

➡️ utils/request.js

class WxRequest {      // coding....       request(options) {    // coding....    // 使用 Promise 封装异步请求    return new Promise((resolve, reject) => {      // 使用 wx.request 发起请求      wx.request({        ...options,        // 接口调用成功的回调函数        success: (res) => {          // 响应成功以后触发响应拦截器          if (this.interceptors.response) {+             // 调用响应拦截器方法,获取到响应拦截器内部返回数据+             // success: true 表示服务器成功响应了结果,我们需要对业务状态码进行判断+             res = this.interceptors.response({ response: res, isSuccess: true })          }          // 将数据通过 resolve 进行返回即可          resolve(res)        },        // 接口调用失败的回调函数        fail: (err) => {          // 响应失败以后也要执行响应拦截器          if (this.interceptors.response) {+             // isSuccess: false 表示是网络超时或其他问题+             err = this.interceptors.response({ response: err, isSuccess: true })          }          // 当请求失败以后,通过 reject 返回错误原因          reject(err)        }                })    })  }      // coding......}// -----------------------------------------------------// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api'})// 设置请求拦截器instance.setRequestInterceptor((config) => {  console.log('执行请求拦截器')  return config})// 设置响应拦截器+ instance.setResponseInterceptor((response) => {+   const { response: res, isSuccess } = response+   // isSuccess: false 表示是网络超时或其他问题,提示 网络异常,同时将返回即可+   if (!isSuccess) {+     wx.toast('网络异常,请稍后重试~')+     // 如果请求错误,将错误的结果返回出去+     return res+   }+  // 简化数据+  return response.data})// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

08. 请求封装-使用请求/响应拦截器

思路分析:

使用请求拦截器:

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

使用响应拦截器:

在使用 wx.request 发送网络请求时。只要成功接收到服务器返回,无论statusCode是多少,都会进入 success 回调。

因此开发者根据业务逻辑对返回值进行判断。

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

  1. 业务状态码 === 200, 说明接口请求成功,服务器成功返回了数据
  2. 业务状态码 === 208, 说明没有 token 或者 token 过期失效,需要登录或者重新登录
  3. 业务状态码 === 其他,说明请求或者响应出现了异常

其他测试接口:/cart/getCartList

落地代码:

➡️ utils/request.js

// 创建 WxRequest 类,采用类的方式进行封装会让方法更具有复用性,也可以方便进行添加新的属性和方法class WxRequest {  // coding...}// -----------------------------------------------------// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',  timeout: 5000})// 设置请求拦截器instance.setRequestInterceptor((config) => {+   // 从本地获取 token+   if (wx.getStorageSync('token')) {+     // 如果存在 token ,则添加请求头+     config.header['token'] = wx.getStorageSync('token')+   }+ +   // 返回请求参数+   return config})// 设置响应拦截器instance.setResponseInterceptor(async (response) => {+   const { response: res, isSuccess } = response+   // isSuccess: false 表示是网络超时或其他问题,提示 网络异常,同时将返回即可+   if (!isSuccess) {+     wx.toast('网络异常,请稍后重试~')+     // 如果请求错误,将错误的结果返回出去+     return res+   }+   switch (res.data.code) {+     case 200:+       return res.data+     case 208:+       // 判断用户是否点击了确定+       const modalStatus = await wx.modal({+         title: '提示',+         content: '登录授权过期,请重新授权'+       })+       // 如果点击了确定,先清空本地的 token,然后跳转到登录页面+       if (modalStatus) {+         wx.clearStorageSync()+         wx.navigateTo({+           url: '/pages/login/login'+         })+       }+       return+     default:+       wx.showToast({+         title: '接口调用失败~~~~',+         icon: 'none'+       })+       // 将错误继续向下传递+       return Promise.reject(response)+   }})// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance

09. 请求封装-添加并发请求

思路分析:

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

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

  1. 使用 asyncawait 方式
  2. 使用 Promise.all() 方式

首先使用asyncawait 方式发送请求,使用 asyncawait 能够控制异步任务以同步的流程执行,代码如下,这时候就会产生一个问题,当第一个请求执行完以后,才能执行第二个请求,这样就会造成请求的阻塞,影响渲染的速度,如下图

在这里插入图片描述

这时候我们需要使用 Promise.all() 方式同时发起多个异步请求,并在所有请求完成后再进行数据处理和渲染。使用Promise.all() 能够将多个请求同时发出,不会造成请求的阻塞。

在这里插入图片描述

通过两种方式演示,我们能够知道封装并发请求的必要性。在 WxRequest 实例中封装 all 方法,使用展开运算符将传入的参数转成数组,方法的内部,使用 Promise.all() 接收传递的多个异步请求,将处理的结果返回即可。

class WxRequest {  // coding...+   // 封装处理并发请求的 all 方法+   all(...promise) {+     return Promise.all(promise)+   }  // coding...}// coding...

在这里插入图片描述

落地代码:

➡️ utils/request.js

class WxRequest {  // coding...+   // 封装处理并发请求的 all 方法+   all(...promise) {+     return Promise.all(promise)+   }  // coding...}// coding...

➡️ /pages/test/test.js

import instance from '../../utils/http'Page({  async getData() {    // 使用 Promise.all 同时处理多个异步请求    const [res1, res2] = await instance.all([      instance.get('/mall-api/index/findBanner'),      instance.get('/mall-api/index/findCategory1')    ])    console.log(res1)    console.log(res2)  }})

10. 请求封装-添加 loading

思路分析:

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

  1. 在请求发送之前,需要通过 wx.showLoading 展示 loading 效果

  2. 当服务器响应数据以后,需要调用 wx.hideLoading 隐藏 loading 效果

要不要加 loading 添加到 WxRequest 内部 ?

  1. 在类内部进行添加,方便多个项目直接使用类提供的 loading 效果,也方便统一优化 wx.showLoading 使用体验。

    但是不方便自己来进行 loading 个性化定制。

  2. 如果想自己来控制 loading 效果,带来更丰富的交互体验,就不需要将 loading 封装到类内部,但是需要开发者自己来优化 wx.showLoading 使用体验,每个项目都要写一份。

大伙可以按照自己的业务需求进行封装,

在项目中我们会选择第一种方式。折中

不过也会通过属性控制是否展示 loading,从而方便类使用者自己控制 loading 显示

落地代码:

➡️ utils/request.js

class WxRequest {  // coding...  constructor(options = {}) {    // coding...  }  // 创建 request 请求方法  request(options) {    // 拼接完整的请求地址    options.url = this.defaults.baseURL + options.url    // 合并请求参数    options = { ...this.defaults, ...options }      +     // 发送请求之前添加 loding+     wx.showLoading()    // 如果存在请求拦截器,我们则调用请求拦截器    if (this.interceptors.request) {      // 请求之前,触发请求拦截器      options = this.interceptors.request(options)    }          // 方法返回一个 Promise 对象    return new Promise((resolve, reject) => {      wx.request({        ...options,        success: (res) => {          // coding...        },        fail: (err) => {          // coding...        },+         complete: () => {+           // 接口调用完成后隐藏 loding+           wx.hideLoading()+         }      })    })  }  // coding...}

11. 请求封装-完善 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 闪烁问题,可以使用定时器来做判断即可

落地代码:

➡️ utils/request.js

class WxRequest {      // coding...      constructor(options = {}) {    // 使用 Object.assign 合并默认参数以及传递的请求参数    this.defaults = Object.assign({}, this.defaults, options)          // 定义拦截器对象,包含请求拦截器和响应拦截器方法,方便在请求或响应之前进行处理。    this.interceptors = {      // 请求拦截器      request: null,      // 响应拦截器      response: null    }+     // 初始化 queue 数组,用于存储请求队列+     this.queue = []  }  // 创建 request 请求方法  request(options) {+     // 如果有新的请求,则清空上一次的定时器+     this.timerId && clearTimeout(this.timerId)         // 拼接完整的请求地址    options.url = this.defaults.baseURL + options.url    // 合并请求参数    options = { ...this.defaults, ...options }    // 如果存在请求拦截器,我们则调用请求拦截器    if (this.interceptors.request) {      // 请求之前,触发请求拦截器      options = this.interceptors.request(options)    }+     // 发送请求之前添加 loding+     this.queue.length === 0 && wx.showLoading()+     // 然后想队列中添加 request 标识,代表需要发送一次新请求+     this.queue.push('request')    // 方法返回一个 Promise 对象    return new Promise((resolve, reject) => {      wx.request({        ...options,        success: (res) => {          // coding...        },        fail: (err) => {          // coding...        },        complete: () => {          // 接口调用完成后隐藏 loding          // wx.hideLoading()+           // 每次请求结束后,从队列中删除一个请求标识+           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)        }      })    })  }  // 封装快捷请求方法  // coding...  // 封装拦截器  // coding...}// coding...export default instance

12. 请求封装-控制 loading 显示

思路分析:

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

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

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

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

实现步骤:

  1. 在 WxRequest 类的默认请求配置项中,设置 isLoading 默认值为 true,显示 loading

    class WxRequest {  // 初始化默认的请求属性  defaults = {    url: '', // 开发者服务器接口地址    data: null, // 请求参数    header: {}, // 设置请求的 header    timeout: 60000, // 超时时间    method: 'GET', // 请求方式+    isLoading: true // 是否显示 loading 提示框  }  // code...}
  2. 在进行实例化的时候,可以配置 isLoading 配置为 false,隐藏 loading

    // 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',+   isLoading: false // 隐藏 loading})
  3. 在发送网络请求时候,传入请求配置 isLoading 配置为 false,隐藏 loading

    async func() {+  // 请求配置 isLoading 配置为 false,隐藏 loading+  await instance.get('/index/findCategory1', null, { isLoading: true })}
  4. wx-request 内部代码实现

    // 创建 WxRequest 类,采用类的方式进行封装会让方法更具有复用性,也可以方便进行添加新的属性和方法class WxRequest {  // 初始化默认的请求属性  defaults = {    url: '', // 开发者服务器接口地址    data: null, // 请求参数    header: {}, // 设置请求的 header    timeout: 60000, // 超时时间    method: 'GET', // 请求方式+     isLoading: true // 是否显示 loading 提示框  }  constructor(params = {}) {    // coding...  }  request(options) {    // coding...+     // 发送请求之前添加 loding+     if (options.isLoading) {+       this.queue.length === 0 && wx.showLoading()+       // 然后想队列中添加 request 标识,代表需要发送一次新请求+       this.queue.push('request')+     }    // 请求之前,触发请求拦截器    // 如果存在请求拦截器,则触发请求拦截器    if (this.interceptors.request) {      options = this.interceptors.request(options)    }    // 使用 Promise 封装异步请求    return new Promise((resolve, reject) => {      // 使用 wx.request 发起请求      wx.request({        ...options,        // 接口调用成功的回调函数        success: (res) => {          // coding...        },        // 接口调用失败的回调函数        fail: (err) => {          // coding...        },        complete: () => {          // 接口调用完成后隐藏 loding          // wx.hideLoading() +          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)        }      })    })  }  // coding...}

13. 请求封装-封装 uploadFile

思路分析:

wx.uploadFile 也是我们在开发中常用的一个 API,用来将本地资源上传到服务器。

例如:在获取到微信头像以后,将微信头像上传到公司服务器。

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

在了解了 API 以后,我们直接对 wx.uploadFile 进行封装即可。

首先在 WxRequest 类内部创建 upload 实例方法,实例方法接收四个属性:

/*** @description 文件上传接口封装* @param { string } url 文件上传地址* @param { string } filePath 要上传文件资源的路径* @param { string } name 文件对应的 key* @param { string } config 其他配置项* @returns */upload(url, filePath, name, config = {}) {  return this.request(    Object.assign({ url, filePath, name, method: 'UPLOAD' }, config)  )}

这时候我们需要在 request 实例方法中,对 method 进行判断,如果是 UPLOAD,则调用 wx.uploadFile 上传API

// request 实例方法接收一个对象类型的参数// 属性值和 wx.request 方法调用时传递的参数保持一致request(options) {    // coding...  // 需要使用 Promise 封装 wx.request,处理异步请求  return new Promise((resolve, reject) => {+     if (options.method === 'UPLOAD') {+       wx.uploadFile({+         ...options,+ +         success: (res) => {+           // 将服务器响应的数据通过 JSON.parse 转换为 JS 对象+           res.data = JSON.parse(res.data)+ +           const mergeRes = Object.assign({}, res, {+             config: options,+             isSuccess: true+           })+ +           resolve(this.interceptors.response(mergeRes))+         },+ +         fail: (err) => {+           const mergeErr = Object.assign({}, err, {+             config: options,+             isSuccess: true+           })+ +           reject(this.interceptors.response(mergeErr))+         },+ +         complete: () => {+           this.queue.pop()+ +           this.queue.length === 0 && wx.hideLoading()+         }+       })    } else {      wx.request({        // coding...      })    }  })}

落地代码:

➡️ utils/request.js

// request 实例方法接收一个对象类型的参数// 属性值和 wx.request 方法调用时传递的参数保持一致request(options) {    // coding...  // 需要使用 Promise 封装 wx.request,处理异步请求  return new Promise((resolve, reject) => {+     if (options.method === 'UPLOAD') {+       wx.uploadFile({+         ...options,+ +         success: (res) => {+           // 将服务器响应的数据通过 JSON.parse 转换为 JS 对象+           res.data = JSON.parse(res.data)+ +           const mergeRes = Object.assign({}, res, {+             config: options,+             isSuccess: true+           })+ +           resolve(this.interceptors.response(mergeRes))+         },+ +         fail: (err) => {+           const mergeErr = Object.assign({}, err, {+             config: options,+             isSuccess: true+           })+ +           reject(this.interceptors.response(mergeErr))+         },+ +         complete: () => {+           this.queue.pop()+ +           this.queue.length === 0 && wx.hideLoading()+         }+       })    } else {      wx.request({        // coding...      })    }  })}

test/test.js

Page({  /**   * 页面的初始数据   */  data: {    avatarUrl: '../../assets/Jerry.png'  },  // 获取微信头像  async chooseavatar(event) {    // 目前获取的微信头像是临时路径    // 临时路径是有失效时间的,在实际开发中,需要将临时路径上传到公司的服务器    const { avatarUrl } = event.detail    // 调用  upload 方法发送请求,将临时路径上传到公司的服务器    const res = await instance.upload(      '/fileUpload',      event.detail.avatarUrl,      'file'    )    // 将返回的数据赋值给 data 中的数据    this.setData({      avatarUrl: res.data    })  },      // coding...}

14. 请求封装-使用 npm 包发送请求

思路分析:

封装的网络请求模块发布到了 npm ,如果你在学习网络请求模块封装时感觉比较吃力,可以先使用 npm 包实现功能。

npm install mina-request

📌 构建 npm:

​ 安装包后,需要在微信开发者工具中进行 npm 构建,点击 工具 ➡️ 构建 npm

其余步骤参考文档进行开发即可:

mina-request 地址

落地代码:

import WxRequest from "./request";import { env } from "./env ";// 是否显示重新登录let isRelogin = { show: false };// ----------------- 实例化 ----------------------// 对 WxRequest 进行实例化const instance = new WxRequest({  baseURL: env.baseURL,  timeout: 15000,});// 配置请求拦截器instance.interceptors.request = (config) => {  // 在发送请求之前做些什么  console.log(config, "在发送请求之前做些什么");  // 从本地获取 token  if (wx.getStorageSync("token")) {    // 如果存在 token ,则添加请求头    config.header["token"] = wx.getStorageSync("token");  }  // 返回请求参数  return config;};// 响应拦截器instance.interceptors.response = (response) => {  console.log(response, "响应拦截器");  const { isSuccess, data } = response;  // isSuccess: false 表示是网络超时或其他问题,提示 网络异常,同时将返回即可  if (!isSuccess) {    wx.showToast({      title: '"网络异常,请稍后重试~"',      icon: "error",    });    // 如果请求错误,将错误的结果返回出去    return response;  }  switch (data.code) {    case 200:      return data;    case 208:      // 控制多个接口触发,弹框只出现一次      if (!isRelogin.show) {        isRelogin.show = true;        wx.showModal({          showCancel: false,          title: "提示",          content: "登录授权过期,请重新授权",          complete: (res) => {            console.log(res);            // 清空token            wx.removeStorageSync("token");            //  返回首页            wx.reLaunch({              url: "/pages/login/login",            });            // 点击确认后恢复状态            isRelogin.show = false;          },        });      }      // 将错误继续向下传递      return Promise.reject(response);    default:      wx.showToast({        title: "接口调用失败~~~~",        icon: "none",      });      // 将错误继续向下传递      return Promise.reject(response);  }};// 将 WxRequest 的实例通过模块化的方式暴露出去export default instance;

15. 环境变量-小程序设置环境变量

知识点:

在实际开发中,不同的开发环境,调用的接口地址是不一样的。

例如:开发环境需要调用开发版的接口地址,生产环境需要调用正式版的接口地址

这时候,我们就可以使用小程序提供了 wx.getAccountInfoSync() 接口,用来获取当前账号信息,在账号信息中包含着 小程序 当前环境版本。

环境版本合法值
开发版develop
体验版trial
正式版release

落地代码:

// 获取当前帐号信息const accountInfo = wx.getAccountInfoSync()// 获取小程序项目的 appIdconsole.log(accountInfo.miniProgram.appId)// 获取小程序 当前环境版本console.log(accountInfo.miniProgram.envVersion)

根据环境的不同,我们给 env 变量设置不同的请求基准路径 baseURL 然后将 env环境变量导出

// 获取 小程序帐号信息const { miniProgram } = wx.getAccountInfoSync();// 获取小程序当前开发环境// develop 开发版, trial 体验版, release 正式版const { envVersion } = miniProgram;let env = {  baseURL: "https://gmall-prod.atguigu.cn/mall-api",};switch (envVersion) {  case "develop":    env.baseURL = "https://gmall-prod.atguigu.cn/mall-api";    break;  case "trial":    env.baseURL = "https://gmall-prod.atguigu.cn/mall-api";    break;  case "release":    env.baseURL = "https://gmall-prod.atguigu.cn/mall-api";    break;  default:    console.log("当前环境异常");    env.baseURL = "https://gmall-prod.atguigu.cn/mall-api";}export { env };

16. 接口调用方式说明

思路分析:

在开发中,我们会将所有的网络请求方法放置在 api 目录下统一管理,然后按照模块功能来划分成对应的文件,在文件中将接口封装成一个个方法单独导出,例如:

// 导入封装的网络请求工具 http.jsimport http from '../utils/http'/** * @description 获取轮播图数据 * @returns Promise */export const reqBannerData = () => http.get('/index/findBanner')

这样做的有以下几点好处:

  1. 易于维护:一个文件就是一个模块,一个方法就是一个功能,清晰明了,查找方便
  2. 便于复用:哪里使用,哪里导入,可以在任何一个业务组件中导入需要的方法
  3. 团队合作:分工合作

落地代码:

// 导入封装的网络请求工具 http.jsimport http from '../utils/http'/** * @description 获取轮播图数据 * @returns Promise */export const reqSwiperData = () => http.get('/mall-api/index/findBanner')
// 导入接口 APIimport { reqSwiperData } from '../../api/index'Page({      // 页面数据  data: {    swiperList: []  },    // 小程序页面加载时执行  onLoad () {    // 调用获取首页数据的方法    getHomeList()  }      // 获取首页数据  async getHomeList() {    // 获取轮播图数据    const res = await reqSwiperData()        console.log(res)  }})

也许您对下面的内容还感兴趣: