前言

iOS中,可以使用rxSwiftmoya便捷的进行封装,而在小程序中,可以使用Promise进行封装,因为jsswift的语言差异,所以有不少借鉴的地方,现在抛砖引玉,总结一下优化点和思考点。

优化点

虽然开发语言和平台不同,但是前端的网络封装优化主要集中于以下几点:

  1. 使用的灵活性和便捷性
  2. 网络请求的token封装
  3. 用户token过期重试
  4. 网络的报错处理:用户网络出错、服务器出错
  5. 业务出错的处理:例如条件不满足等报错

结合以上几点思路,在小程序端慢慢进行改造

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.requesttimeout字段,读取的data里面的data._timeout,当然_request也可以多封装一个参数,比如configconst _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)
            }
        });
    })
}

重试后的效果是这样

截屏2023-08-01 18.08.35.png

更多说明

扫码关注公众号,发送
1230
自动获取

☟☟可点击下方广告支持一下☟☟

最后修改:2023 年 08 月 01 日
请我喝杯可乐,请随意打赏: ☞已打赏列表