1 问题
在使用小程序小程序原生的请求方法wx.request时,发现以下几个问题,不利于统一管理
- 1 不支持
async/await,不太好处理请求 - 2 请求的头每次都得重新写一遍,登录使用的token注入麻烦
- 3 请求的异常状态码散乱,因为请求异常,后台都是传递code加上英文说明,而前端为了友好呈现,一般需要进行中文的映射
2 解决
async/await支持
之前都是手动引入第三方的库regenerator实现的,后面微信开发者工具的增强编译中增加的自动引入async/await的支持,无需自己引入第三方库
- 勾选增强编译

请求封装
- 目录结构
utils
|---- wx-request.js 封装请求
|---- api.js 所有请求的地址
|---- expectionalError.js 请求异常映射
|---- storageSyncTool.js 本地存储工具类
封装请求wx-request.js
这里主要做了一下几件事 1.引入请求异常的映射,方便出现已知的异常时,对异常信息统一翻译 2.引入本地存储封装 3.指定服务器请求根地址 4.在封装的请求中,接收传递传递过来的自定义请求参数,将自定义请求参数、默认参数进行合并,将token注入请求头中 5.发起请求时,校验token是否存在,如果存在,发起请求,如果不存在,直接拒绝请求 6.请求完成后,判断请求码code
- 请求正常
code &&code === 200(这里根据实际情况做判断),将数据返回 - 请求失败
- 进行异常处理中,使用异常状态码换区错误提示信息
- 异常状态码是
406,提示用户重新登录
import getErrorMessage from "./expectionalError"
import {
getLoginToken,
clearLoginToken
} from "./storageSyncTool.js.js"
// host地址
const host = "https://www.abc.com"
// 合并请求参数: 将默认参数和实际请求的参数进行合并
const mergeRequestParmas = (perDefaults, params) => {
return Object.assign({}, perDefaults, params)
}
// 请求封装
const wxRequest = async (subUrl, params = {}) => {
const token = getLoginToken()
const defaults = {
header: {
"Content-Type": "application/json",
token: token || ""
},
method: "GET",
data: {}
}
// 合并参数
const options = mergeRequestParmas(defaults, params)
let res = await new Promise((resolve, reject) => {
if (!token) {
reject("登录过期")
}
let url = host + subUrl
const { header, method, data } = options
wx.request({
url,
header,
method,
data,
success: res => {
const { code, data } = res.data
if (res && code === 200) {
resolve(data)
} else {
let err = {
request: res.request,
response: {
data: { status: res.data.code, description: res.data.message }
}
}
const handlerErr = responseErrorHandler(err)
res.err = handlerErr
reject(res)
}
},
fail: err => {
reject(err)
},
complete: e => {}
})
})
return res
}
/**
* 统一处理响应错误
* @param {object} error
*/
const responseErrorHandler = error => {
// 自定义错误
let err = {
title: "未知错误",
description: "系统发生未知的错误"
}
if (error.response) {
// 发送请求后,服务端有返回
// 1. HTTP返回的响应码不是 2xx
// 2. 服务端自定义错误,服务端响应码不是 2xx
// 3. 客户端自定义错误,返回的数据被定义为错误状态
err = getErrorMessage(error)
if (err.code === 406) {
const token = getLoginToken()
if (token) {
clearLoginToken()
//之前有token,token过期
loginDialog()
} else {
loginDialog()
}
return false
}
} else if (error.request) {
// 发送请求但是没有响应返回
err.title = "服务器忙"
err.description = "服务器繁忙,请稍后重试"
}
return err
}
export { wxRequest }
请求异常映射expectionalError.js
主要分为三大块: 1.常见请求的基本错误码 2.业务相关的错误码 3.未识别的错误码
//异常信息统一处理
const HTTP_ERROR = {
HTTP_DEFALUT_ERROR: new Map([
[400, '请求参数错误'],
[401, '请求要求用户的身份认证'],
[403, '服务器拒绝执行此请求'],
[404, '请求的资源无法找到'],
[405, '请求中的方法被禁止'],
[406, '服务器无法根据客户端请求的内容特性完成请求'],
[407, '请求要求代理的身份认证'],
[408, '请求超时'],
[409, '请求存在冲突'],
[410, '客户端请求的资源已经不存在'],
[411, '服务器无法处理不带Content-Length的请求信息'],
[412, '请求信息的先决条件错误'],
[413, '请求的实体过大'],
[414, '请求的URI过长'],
[415, '无法处理请求附带的媒体格式'],
[416, '无效的请求范围'],
[417, '无法满足Expect的请求头信息'],
[500, '服务器内部错误'],
[501, '服务器不支持该请求功能'],
[502, '请求失效'],
[503, '服务器暂时的无法处理请求'],
[504, '无法获取请求'],
[505, 'http协议版本错误']
]),
CUSTOM_ERROR: new Map([
[404, '请求数据不存在'],
[406, '登陆过期,请重登'],
[200201, '乘客登录验证码发送失败'],
[200202, '验证码已过期,请重新获取'],
[200203, '短信验证码错误'],
[200501, '临时code为空'],
[200502, '参数配置错误'],
[301001, '资源不可达'],
[301002, '未查询出匹配数据'],
[301003, '输入的城市在有效范围中'],
[301004, '短信验证码错误'],
[301003, '高德查询主机解析错误']
// ....
])
}
const getErrorMessage = function (error) {
let response = error.response.data
let err = {
title: '未知错误',
description: '系统发生未知的错误'
}
if (typeof response === 'string') {
err.description = '服务器异常,请稍后重试'
} else if (error.response.status >= 400 && error.response.status < 600) {
err.title = (error.response.status < 500) ? '请求错误' : '服务器错误'
err.description = HTTP_ERROR.HTTP_DEFALUT_ERROR.get(error.response.status)
} else {
err.code = response.status
err.title = '后台系统错误'
err.description = (response && 'status' in response && HTTP_ERROR.CUSTOM_ERROR.has(response.status)) ? HTTP_ERROR.CUSTOM_ERROR.get(response.status) : '操作失败,请稍后重试'
}
return err
}
export default getErrorMessage
请求api
所有业务请求的api接口,包含h5链接,在wx-request.js请求模块中通过host+api接口拼接的方式组成完整的请求地址
const api = {
// 获取证码
'SMS_CODE': '/usr/passenger/captcha',
// 乘客登录
'PASSENGER_LOGIN': '/usr/passenger/wechat-login',
// 意见反馈
'FEEDBACK': '/usr/setting/feedback',
// 根据乘客输入的城市和上下车地点查询地点的相关信息(模糊查询
'QUERY_SITE': '/usr/amap/obscureQuerySite',
// 订单列表
'ORDER_LIST': '/my/order/list',
// 收费说明
'CHARGE_DESCRIPTION': '/h5/charge-description.html',
// 免责协议
'DISCLAIMER_AGREEMENT': '/h5/disclaimer-agreement.html'
}
export default api
本地存储storageSyncTool.js
这个模块中主要做2件事,
- 1.定义登录后token的键名,定义存储/获取登录token的方法
getLoginToken/setLoginToken,请除登录token的方法clearLoginToken - 2.定义通用的存储/获取本地存储的方法
getStorage/setStorage,清除方法clearStorage
// 定义登录token的key(键名),在获取/存储的时候,通过这个key获取和存储
const LOGIN_TOKEN_KEY = 'customerTokenName'
const setStorage = (key, value) => {
try {
wx.setStorageSync(key, value)
return true
} catch (e) {
return false
}
}
const getStorage = (key) => {
try {
const value = wx.getStorageSync(key)
return value
} catch (e) {
return false
}
}
const clearStorage = key => {
try {
wx.clearStorageSync(key)
return true
} catch (e) {
return false
}
}
const setLoginToken = (value) => {
return setStorage(LOGIN_TOKEN_KEY, value)
}
const getLoginToken = () => {
return getStorage(LOGIN_TOKEN_KEY)
}
const clearLoginToken = () => {
return clearStorage(LOGIN_TOKEN_KEY)
}
module.exports = {
setLoginToken,
getLoginToken,
getStorage,
setStorage,
clearLoginToken,
clearStorage
}
使用封装请求的简单示例
- 1 GET请求
导入api模块和请求模块,取出对应的api接口,准备请求数据,最后发送请求
import API from '../../utils/api.js';
import { wxRequest } from '../../utils/wx-request.js';
Page({
//....
getList() {
const that = this
// 获取请求api
const url = API.ORDER_LIST
const { page, size } = that.data
const params = {
page,
size
}
wxRequest(url, {
data: params
}).then(data => {
if (data && data.length > 0) {
that.setData({
list: data
})
}
}).catch(res => {
const { description } = res.err
console.log(description)
})
}
})
- 2 POST请求
import API from '../../utils/api.js';
import { wxRequest } from '../../utils/wx-request.js';
Page({
submitFeedback() {
const that = this
const { suggestion, phoneNum } = that.data
const url = API.FEEDBACK
const params = {
suggestion,
phoneNum
}
wxRequest(url, {
method: 'POST',
data: params
}).then(res => {
wx.showToast({
title: '提交反馈成功',
mask: true,
icon: 'success',
duration: 3000,
success: function() {
setTimeout(() => {
wx.navigateBack({
delta: 1
})
}, 3000)
}
})
}).catch(res => {
const { description } = res.err
console.log(description)
})
}
})
3 其它问题
通过上面的封装,可以比较好的将api,请求,请求调用和异常信息处理分模块处理,方便后面的维护,但是还有一些问题还是可以优化的 比如,环境的切换(一般都有开发、测试、生产环境,都对应不同的地址)
环境管理
在上面的请求封装中,host地址只有一个,在使用的时候将host+api接口的方式组成完整的地址每次切换环境都得修改host变量,比较麻烦,并且实际生产中,不同环境除了host不同外,h5地址,wss,第三方key都是不同的,一套环境同时维持不同请求内容.所有需要一个父级进行包裹它们,切换环境,只需要更改父级即可.
定义环境管理env.js
// 运行环境配置文件
// 开发环境
const dev = {
host: 'https://dev.qq.cn',
ws: 'wss://dev.qq.cn',
h5: 'https://dev.qq.cn',
label: 'dev',
}
// 测试环境
const test = {
host: 'https://test.qq.cn',
ws: 'wss://test.qq.cn',
h5: 'https://test.qq.cn',
label: 'test'
}
// 生产环境
const prod = {
host: 'https://prod.qq.cn',
ws: 'wss://prod.qq.cn',
h5: 'https://prod.qq.cn',
label: 'prod'
}
module.exports = {
domain: prod
}
在请求模块中使用
- 导入env.js
- 拼接完整地址使用
let url = env.domain.host + subUrl方式,代替之前的host+subUrl方式
import env from './env.js';
// 请求封装
const wxRequest = async (subUrl, params = {}) => {
const token = getLoginToken()
const defaults = {
header: {
"Content-Type": "application/json",
token: token || ""
},
method: "GET",
data: {}
}
// 合并参数
const options = mergeRequestParmas(defaults, params)
let res = await new Promise((resolve, reject) => {
if (!token) {
reject("登录过期")
}
let url = env.domain.host + subUrl
const { header, method, data } = options
wx.request({
url,
header,
method,
data,
success: res => {
const { code, data } = res.data
if (res && code === 200) {
resolve(data)
} else {
let err = {
request: res.request,
response: {
data: { status: res.data.code, description: res.data.message }
}
}
const handlerErr = responseErrorHandler(err)
res.err = handlerErr
reject(res)
}
},
fail: err => {
reject(err)
},
complete: e => {}
})
})
return res
}
//.......
export {
wxRequest
}