网络请求封装
网络请求模块难度较大,如果学习起来感觉吃力,可以直接学习
[请求封装-使用 npm 包发送请求]
以后的模块
01. 为什么要封装 wx.request
小程序大多数 API 都是异步 API,如 wx.request(),wx.login() 等。这类 API 接口通常都接收一个 Object
对象类型的参数,参数中可以按需指定以下字段来接收接口调用结果:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
success | function | 否 | 调用成功的回调函数 |
fail | function | 否 | 调用失败的回调函数 |
complete | function | 否 | 调用结束的回调函数(调用成功、失败都会执行) |
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
}
封装后网络请求模块包含以下功能
- 包含 request 实例方法发送请求
- 包含 get、delete、put、post 等实例方法可以快捷的发送网络请求
- 包含 请求拦截器、响应拦截器
- 包含 uploadFile 将本地资源上传到服务器 API
- 包含 all 并发请求方法
- 同时优化了并发请求时 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'
})
从而得出结论:请求参数的设置有三种方式:
- 默认参数:在
WxRequest
类中添加defaults
实例属性来设置默认值 - 实例化时参数:在对
WxRequest
类进行实例化时传入相关的参数,需要在constructor
构造函数形参进行接收 - 调用实例方法时传入请求参数
默认参数和自定义参数的合并操作,通常会在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