慕尚花坊 --- 通用模块封装
0. 为什么模块封装
在项目开发的时候, 经常会频繁的使用到一些 API
, 例如: wx.showToast()
、wx.showModal()
等消息提示 API
,每次使用这些小程序提供的原生 API
, 会导致代码冗余,所以将其封装成公共方法来简化 API
的调用
1. 消息提示框
1. 封装思路
- 创建一个
toast()
对wx.showToast()
进行封装 - 调用该方法时, 传递对象作为参数
- 如果没有传递任何参数, 则使用默认的空对象
{}
作为参数 - 对象中包含
title
、icon
、duration
、mask
参数,并设置默认值
- 如果没有传递任何参数, 则使用默认的空对象
- 在需要显示提示框的时候, 直接调用
toast()
, 并传入相关参数- 不传递参数, 使用默认值
- 传入部分参数, 覆盖默认参数
- 封装后的提示框有两种调用方式
- 模块化的导入使用
- 将对象挂载到 全局
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. 封装思路
- 创建一个
modal()
对wx.showModal()
进行封装 - 调用该方法时, 传递对象作为参数, 对象的参数与
wx.showModal()
保持一致 - 在需要显示提示框的时候, 直接调用
modal()
, 并传入相关参数- 不传递参数, 使用默认值
- 传入部分参数, 覆盖默认参数
- 封装后的提示框有两种调用方式
- 模块化的导入使用
- 将对象挂载到 全局
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
对象中不包含 success
、fail
、complete
时, 将默认返回 promise
, 否则按照回调方式执行,无返回值
但是部分接口, 如: downloadFile
、request
、uploadFile
等本身就有返回值, 因此不支持 promise
调用方式, 它们的 promisify
需要开发者自行封装。
Axios
是日常开发中常用的一个基于 promise
的网络请求库, 可以参考 Axios
的 [使用方式] 来封装自己的网络请求模块
1. 封装 request 方法
1. 封装思路
- 在封装网络请求模块的Hi好, 采用
Class
类来进行封装, 这样代码更具可复用性, 也方便添加新的方法和属性,提高代码的扩展性 - 在
WxRequest
类内部封装一个request
实例方法,request
实例方法中需要使用Promise
封装wx.request()
, 也就是使用Promise
处理wx.request
的返回结果。request
实例方法接收一个options
对象作为形参,options
参数和调用wx.request()
时传递的请求配置项一致- 接口调用成功时, 通过
resolve
返回响应数据 - 接口调用失败时, 通过
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. 三种参数设置
- 在发起网络请求时, 通常需要配置一些请求参数。其中一些参数可以设置为默认参数。如:
请求方法
、超时时长
等 - 但是不同项目的请求参数设置是不同个, 还需要允许在进行实例化的时候, 传入参数, 对默认参数进行修改
- 在通过实例调用
request
实例方法时也会传入相关的请求参数
2. 封装思路
- 默认参数: 在
WxRequest
类中添加defaults
实例属性来设置默认值 - 实例化时参数: 在对
WxRequest
类进行实例化时传入相关参数, 需要在constructor
构造函数的形参进行接收 - 调用时参数: 调用
request
实例方法时传入请求参数 - 默认参数和自定义参数的合并操作, 通常会在
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. 快速请求
封装四个快捷方法, get
、post
、put
、delete
用来快速发送对应请求方式的请求
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 注意事项
- 只要成功接收到服务器的返回结果, 无论
statusCode
状态码是多少, 都会执行success
回调函数 - 在
网络异常
、请求超时
时, 才会执行fail
回调函数
5. 封装 请求 & 响应 拦截器
为了方便统一处理请求参数以及服务器相应结果, 为 WxRequest
添加拦截器功能
- 请求拦截器: 在请求之前调用的函数, 用来对请求参数进行新增和修改
- 响应拦截器: 在响应之后调用的函数, 用来响应数据进行额外操作
在发送请求时, 还需要区分是否通过实例调用了拦截器
- 没有通过实例调用拦截器, 需要定义默认拦截器, 在默认拦截器中,需要将请求参数返回
- 通过实例调用拦截器, 那么实例调用的拦截器会覆盖默认的拦截器, 然后将新增或修改的请求参数进行返回
1. 封装思路
- 在
WxRequest
类内部定义interceptors
实例属性, 属性中包含request
、response
方法 - 是否通过实例调用拦截器
- 否: 定义默认拦截器
- 是: 实例调用的拦截器覆盖默认拦截器
- 在发送请求之前, 调用请求拦截器
- 在服务器响应以后, 调用响应拦截器, 不管成功、失败响应, 都需要调用响应拦截器
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. 封装思路
- 如果请求成功, 将响应成功的数据传递给响应拦截器, 同时在传递的数据中新增
isSuccess: true
字段, 表示请求成功 - 如果请求失败, 将响应失败的数据传递给响应拦截器, 同时在传递的数据中新增
isSuccess: false
字段, 表示请求失败 - 在实例调用响应拦截器时, 如果
isSuccess: true
, 则表示服务器成功响应了结果, 就可以将数据简化以后返回 - 在实例调用响应拦截器时, 如果
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. 思路分析
- 在发送请求时, 购物车列表、收货地址、更新头像等接口, 都需要进行权限验证, 因此需要在请求拦截器中判断本地是否存在访问令牌
token
, 如果存在就需要在请求头中添加token
字段 - 在使用
wx.request
发送网络请求时, 只要成功接收到服务器返回结果, 无论statusCode
是多少, 都会进入success
回调, 因此需要对业务状态码进行逻辑判断- 业务状态码 === 200, 说明接口请求成功, 服务器成功返回了数据
- 业务状态码 === 208, 说明没有
token
或者token
过期失效, 需要登录或重新登录获取最新token
- 业务状态码 === 其他, 说明请求或响应出现了异常
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. 使用 async
和 await
方式, 同时发送多个请求
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
效果, 从而提高用户使用体验
- 在发送请求之前, 需要通过
wx.showLoading
展示loading
效果 - 在服务器响应数据以后, 需要通过
wx.hideLoading
隐藏loading
效果
2. 要不要加 loading
到 WxRequest
内
- 在类内部进行添加, 方便多个项目直接使用类提供的
loading
效果, 也方便统一优化wx.showLoading
使用体验。但是不方便自己进行loading
的个性化定制 - 如果想自己来控制
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
的展示和隐藏会存在以下问题:
- 每次请求都会执行
wx.showLoading()
, 但是页面中只会显示一个, 后面的loading
会将前面的覆盖 - 同时发起多次请求, 只要有一个请求成功响应就会调用
wx.hideLoading()
, 导致其他请求还没完成, 也不会展示loading
请求过快
或一个请求依赖于另一请求来触发
, 这时候会出现loading
闪烁问题
2. 可以通过 队列
的方式解决上面的三个问题
- 首先在类中新增一个实例属性
queue
, 初始值是一个空数组 - 发起请求之前, 判断
queue
如果是空数组则显示loading
, 然后立即向queue
新增请求表示 - 在
complete
中每次请求成功结束, 从queue
中移除一个请求表示,queue
为空时隐藏loading
- 为了解决网络请求过快产生
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
效果
- 类内部设置默认请求参数
isShowLoading
属性, 默认值为true
, 在类内部根据isShowLoading
属性做判断即可 - 如果某个接口不需要显示
loading
效果时, 可以在发送请求的时候, 新增请求配置isShowLoading: false
- 如果整个项目都不需要显示
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