前言
在iOS
中,可以使用rxSwift
和moya
便捷的进行封装,而在小程序中,可以使用Promise
进行封装,因为js
和swift
的语言差异,所以有不少借鉴的地方,现在抛砖引玉,总结一下优化点和思考点。
优化点
虽然开发语言和平台不同,但是前端的网络封装优化主要集中于以下几点:
- 使用的灵活性和便捷性
- 网络请求的token封装
- 用户token过期重试
- 网络的报错处理:用户网络出错、服务器出错
- 业务出错的处理:例如条件不满足等报错
结合以上几点思路,在小程序端慢慢进行改造
1、灵活性
Promise
采用的resolve
, reject
可以很简单的处理回调,而通过.then
、.catch
处理不同的结果,比层层的回调判断更加简洁。
小程序的网络请求wx.request
,参数是一个对象,这样的好处就是可以随时拓展新参数。所以在封装的时候,请求的参数、配置也更倾向于封装成对象,这点和swift
不一样
在swift
中,参数可以根据key值缺省参数,可以跳过某个参数或者单独设置某个参数,例如下面的这个可以只设置content和direction
func show(_ content: String? = nil, icon: HDHUDIconType = .none, direction: HDHUDContentDirection = .horizontal, duration: TimeInterval = 2.5, superView: UIView? = nil, mask: Bool = false, priority: HDHUDPriority = .high) {}
而在js
中,参数是按照顺序去读取,如果中间加某个参数,则原来的顺序也会变,例如
const transformUA = function (data, data1, data3) {
return data + data3
}
//调用
transformUA(1,2,3)
如果在data3之前插入了一个data2,再使用该函数就会需要修改调用的数据顺序,拓展起来比较麻烦。
所以在设计之初,就推荐将参数设置为对象,缺点也显而易见,不会像单独参数那样在ide中有代码提醒,但是考虑拓展性,可以忽略。
//发起请求
const _request = async (url, data, host) => {
let method = data._method ?? 'POST'
//请求域名
let requestHost = params.ApiHost[host] ?? params.ApiHost.host1
//发起请求
return new Promise(function (resolve, reject) {
let requestURL = requestHost + url
wx.request({
url: requestURL,
header: {
'content-type': 'application/x-www-form-urlencoded'
},
data: data,
method: method,
timeout: data._timeout ?? 30000,
success: function (res) {
dLog.netLog(requestURL, data, res, method, true)
//优化服务器返回response为null的情况
let ok = util.safePropertyValue(res, "data.ok", null)
if (ok == 1) {
resolve(res.data)
} else if (util.isEmpty(ok)) {
reject({
ok: res.statusCode,
message: `服务器状态码错误,请切换网络重试。状态码: ${res.statusCode}`
})
} else {
reject(res.data)
}
},
fail: function (res) {
dLog.netLog(requestURL, data, res, method, false)
let ok = util.safePropertyValue(res, "data.ok", null)
if (util.isEmpty(ok)) {
res = {
data: {
ok: -999,
message: `网络错误: ${res.errMsg} 错误码: ${res.errno}`
}
}
}
reject(res.data)
},
})
})
}
所以这个示例,将请求参数和可配置的参数封装到data
对象,而不是单独的参数,例如wx.request
的timeout
字段,读取的data
里面的data._timeout
,当然_request
也可以多封装一个参数,比如config
,const _request = async (url, data, config, host) => {}
,我这里采用的是全部放到data,减少一个变量,如果是配置的属性,则前面加_
,例如超时时间_timeout
、请求类型_method
、重试次数_retry
2、网络请求的token封装
网络token的封装是基本操作,比如请求参数加密、请求头添加固定值、公共请求参数等,这些功能在request文件封装即可
3、重试逻辑
请求失败,正常就是再继续重试。而是否重试则需要根据不同的情况进行判断
3.1、用户token过期重试和业务出错
业务出错指用户条件不满足,token过期算是一种特殊的业务出错。一般业务出错是已经判断确定,不需要再请求重试,重试反而影响服务器资源,而token过期则需要采用自动刷新或者重新登陆之后重新请求接口重试。
业务出错大部分都是采用不同的code标记,例如这个例子,当ok值为-4时token过期,其他为业务错误
if (ok == 1) {
resolve(res.data)
} else if (res.ok == -4) {
await logout()
//重新登录
return codeLogin().then(_retry.bind(null, fn, url, data)).then(resolve).catch(reject)
} else {
let _showError = data._showError ?? true
if (_showError) {
ui.showToast('' + res.message)
}
//业务出错不重试
reject(res)
}
3.2、网络错误的处理
网络错误可能原因是用户网络问题、基站问题或者服务器宕机的问题,这种可以采取失败重试的方案。例如上面的demo中,在fail回调手动设置了ok值为-999
,所以结合上面的token报错和业务报错,重试功能可以封装为
//网络请求
const request = async (url, data = {}, host) => {
return _retry(_request, url, data, host)
}
//重试函数
const _retry = async (fn, url, data, host) => {
var retry = data._retry ?? 6
return new Promise((resolve, reject) => {
return fn(url, data, host).then(resolve).catch(async (res) => {
if (res.ok == -999) {
//网络错误,请求重试
if (retry > 0) {
dLog.color(`${url} 重试====>${retry}`)
data._retry = retry - 1
return wait(2000)
.then(_retry.bind(null, fn, url, data))
.then(resolve)
.catch(reject);
} else {
let _showError = data._showError ?? true
if (_showError) {
ui.showToast('' + res.message)
}
return reject(res)
}
} else if (res.ok == -4 || res.ok == -6 || res.ok == 107) {
await logout()
//重新登录
return codeLogin().then(_retry.bind(null, fn, url, data)).then(resolve).catch(reject)
} else {
let _showError = data._showError ?? true
if (_showError) {
ui.showToast('' + res.message)
}
//业务出错不重试
reject(res)
}
});
})
}
重试后的效果是这样
更多说明
版权属于:东哥笔记 - DongGe.me
本文链接:https://dongge.org/blog/1230.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!