赞
踩
网络请求模块难度较大,如果学习起来感觉吃力,可以直接学习
[请求封装-使用 npm 包发送请求]
以后的模块
小程序大多数 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 }
封装后网络请求模块包含以下功能
思路分析:
在封装网络请求模块的时候,采用 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) } })
思路分析:
在发起网络请求时,需要配置一些请求参数,
其中有一些参数我们可以设置为默认参数,例如:请求方法、超时时长 等等,因此我们在封装时我们要定义一些默认的参数。
// 默认参数对象
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({}, 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
思路分析:
目前已经完成了 request()
请求方法的封装,同时处理了请求参数。
每次发送请求时都使用 request()
方法即可,但是项目中的接口地址有很多,不是很简洁
const res = await instance.request({
url: '/index/findBanner',
method: 'GET'
})
所以我们在 request()
基础上封装一些快捷方法,简化 request()
的调用。
需要封装 4 个快捷方法,分别是 get
、delete
、post
、put
,他们的调用方式如下:
instance.get('请求地址', '请求参数', '请求配置')
instance.delete('请求地址', '请求参数', '请求配置')
instance.post('请求地址', '请求参数', '请求配置')
instance.put('请求地址', '请求参数', '请求配置')
这 4 个请求方法,都是通过实例化的方式进行调用,所以需要 Request
类中暴露出来 get
、delete
、post
、put
方法。每个方法接收三个参数,分别是:接口地址、请求参数以及其他参数。
这 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) } })
知识点:
在使用 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)
}
})
}
思路分析:
为了方便统一处理请求参数以及服务器响应结果,为 WxRequest
添加拦截器功能,拦截器包括 请求拦截器 和 响应拦截器
请求拦截器本质上是在请求之前调用的函数,用来对请求参数进行新增和修改
响应拦截器本质上是在响应之后调用的函数,用来对响应数据做点什么
注意:不管成功响应还是失败响应,都会执行响应拦截器
拦截器的使用方式:
// 请求拦截器
instance.interceptors.request = (config) => {
// 在发送请求之前做些什么
return config
}
// 响应拦截器
instance.interceptors.response = (response) => {
// 对响应数据做点什么
return response
}
通过使用方式,我们可以得出结论:
可以在 WxRequest
类内部定义 interceptors
实例属性,属性中需要包含 request
以及 response
方法
需要注意:在发送请求时,还需要区分是否通过实例调用了拦截器:
实现拦截器的思路:
WxRequest
类内部定义 interceptors
实例属性,属性中需要包含 request
以及 response
方法在 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
思路分析:
在响应拦截器,我们需要判断是请求成功,还是请求失败,然后进行不同的业务逻辑处理。
例如:请求成功以后将数据简化返回,网络出现异常则给用户进行网络异常提示。
目前不管请求成功 (success),还是请求失败(fail),都会执行响应拦截器
那么怎么判断是请求成功,还是请求失败呢 ?
封装需求:
isSuccess: true
字段,表示请求成功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
思路分析:
使用请求拦截器:
在发送请求时,购物车列表、收货地址、更新头像等接口,都需要进行权限验证,因此我们需要在请求拦截器中判断本地是否存在访问令牌 token
,如果存在就需要在请求头中添加 token
字段。
使用响应拦截器:
在使用 wx.request 发送网络请求时。只要成功接收到服务器返回,无论statusCode
是多少,都会进入 success
回调。
因此开发者根据业务逻辑对返回值进行判断。
后端返回的业务状态码如下:
其他测试接口:/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
思路分析:
前端并发请求是指在前端页面同时向后端发起多个请求的情况。当一个页面需要请求多个接口获取数据时,为了提高页面的加载速度和用户体验,可以同时发起多个请求,这些请求之间就是并发的关系。
我们通过两种方式演示发起多个请求:
async
和 await
方式Promise.all()
方式首先使用async
和 await
方式发送请求,使用 async
和 await
能够控制异步任务以同步的流程执行,代码如下,这时候就会产生一个问题,当第一个请求执行完以后,才能执行第二个请求,这样就会造成请求的阻塞,影响渲染的速度,如下图
这时候我们需要使用 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)
}
})
思路分析:
在封装时添加 loading
效果,从而提高用户使用体验
在请求发送之前,需要通过 wx.showLoading
展示 loading
效果
当服务器响应数据以后,需要调用 wx.hideLoading
隐藏 loading
效果
要不要加 loading 添加到 WxRequest 内部 ?
在类内部进行添加,方便多个项目直接使用类提供的 loading 效果,也方便统一优化 wx.showLoading 使用体验。
但是不方便自己来进行 loading 个性化定制。
如果想自己来控制 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... }
思路分析:
目前在发送请求时,请求发送之前会展示 loading
,响应以后会隐藏 loading
。
但是 loading 的展示和隐藏会存在以下问题:
wx.showLoading()
,但是页面中只会显示一个,后面的 loading
会将前面的覆盖wx.hideLoading
,导致其他请求还没完成,也不会 loading
loading
闪烁问题我们通过 队列 的方式解决这三个问题:首先在类中新增一个实例属性 queue
,初始值是一个空数组
queue
如果是空数组则显示 loading
,然后立即向queue
新增请求标识complete
中每次请求成功结束,从 queue
中移除一个请求标识,queue
为空时隐藏 loading
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
思路分析:
在我们封装的网络请求文件中,通过 wx.showLoading
默认显示了 loading
效果
但是在实际开发中,有的接口可能不需要显示 loading
效果,或者开发者希望自己来控制 loading
的样式与交互,那么就需要关闭默认 loading
效果。
这时候我们就需要一个开关来控制 loading
显示。
isLoading
属性,默认值是 true
,在类内部根据 isLoading
属性做判断即可loading
效果,可以在发送请求的时候,可以新增请求配置 isLoading
设置为 false
loading
效果,可以在实例化的时候,传入 isLoading
配置为 false
实现步骤:
在 WxRequest 类的默认请求配置项中,设置 isLoading 默认值为 true,显示 loading
class WxRequest {
// 初始化默认的请求属性
defaults = {
url: '', // 开发者服务器接口地址
data: null, // 请求参数
header: {}, // 设置请求的 header
timeout: 60000, // 超时时间
method: 'GET', // 请求方式
+ isLoading: true // 是否显示 loading 提示框
}
// code...
}
在进行实例化的时候,可以配置 isLoading 配置为 false,隐藏 loading
// 对 WxRequest 进行实例化
const instance = new WxRequest({
baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
+ isLoading: false // 隐藏 loading
})
在发送网络请求时候,传入请求配置 isLoading 配置为 false,隐藏 loading
async func() {
+ // 请求配置 isLoading 配置为 false,隐藏 loading
+ await instance.get('/index/findCategory1', null, { isLoading: true })
}
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... }
思路分析:
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... }
思路分析:
封装的网络请求模块发布到了 npm
,如果你在学习网络请求模块封装时感觉比较吃力,可以先使用 npm 包实现功能。
npm install mina-request
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/460819
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。