起因:
接手修改并维护一个“微信小程序”项目,看了下整个项目的代码,发现写的比较随意,可维护性差,所有的接口请求全都是用wx.request()写的,没有进一步封装,没统一捕获异常和错误提醒了。本着基于原有的代码封装request并最小程度的改之前所有的wx.request()代码。
原有代码:(看着就头疼,强迫症的估计都看不下去,所有的接口都是这样写,都不闲累的...)
其一:(success回调http状态200之类的执行)
wx.request({
url: app.globalData.http + '/某接口地址',
header: {
'Api-Ext': app.globalData.apiExt
},
data: {
page: page
},
dataType: 'json',
method: 'GET',
success: function (data) {
if (data.statusCode >= 200 && data.statusCode < 300) {
if (params === 'isConcat' && data.data.length > 0) {
let tempArr = that.data.good
let newArr = tempArr.concat(data.data)
that.setData({
good: newArr
})
} else if (params === 'isConcat' && data.data.length == 0) {
let newPage = that.data.currentPage
newPage--
that.setData({
currentPage: newPage
})
} else {
that.setData({
good:data.data
})
}
resolve()
} else {
reject()
}
},
fail: function (data) {
reject()
},
complete() {
wx.hideLoading()
}
})
其二:(success回调需要通过statusCode来进行不同逻辑处理的代码)
wx.request({
url: app.globalData.http + '/某接口地址',
method: 'GET',
dataType: 'json',
header: {
"Api-Key": app.globalData.apiKey,
"Api-Secret": app.globalData.apiSecret,
'Api-Ext': app.globalData.apiExt
},
success: function (data) {
if (data.statusCode == 200) {
that.setData({
info: data.data,
pageVisible: true
})
if (data.data.status == 200) {
var time = data.data.expire_at
that.setInterval(time)
}
} else { // 这边是要通过statusCode来进行不同逻辑处理的代码
wx.showToast({
title: '订单不存在',
icon: 'none',
duration: 3000
})
that.setData({
pageVisible: false
})
}
},
complete() {
wx.hideLoading()
}
从上面的代码我们可以发现几点不合理的地方:
- 每次都带同一个header: { 'Api-Ext': app.globalData.apiExt }。
- url每次都拼接同一个域名:url: app.globalData.http + '/某接口地址'。
- 多写dataType: 'json',json格式是wx.request()默认的。
- 多写method: 'GET',也是默认的。
- success回调里面每次都判断接口是否200,if (data.statusCode >= 200 && data.statusCode < 300){}。
- 非200的接口没提醒,用户一接口异常,就一脸懵逼啥提醒都没。
- 没做fail回调的错误提醒。
- 每次都写关闭loading的代码: complete() {wx.hideLoading()}。
基于以上几点问题就自己尝试着封装一个request.js
改动后的代码:
request.js:
// app.js中获取不到getApp()
let globalData = '' // 接口domain
// 成功http状态码
const successCodes = [200, 201, 202, 204, 304]
// http状态码对应的默认文本提醒
const codeMessage = {
200: '操作成功',
201: '新建或修改数据成功',
202: '一个请求已经进入后台排队(异步任务)',
204: '删除数据成功。',
400: '参数错误',
401: '需要用户验证',
403: '用户无权限',
404: '资源不存在',
405: '不支持的操作方法',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的',
422: '当创建一个对象时,发生一个验证错误',
500: '服务器内部错误',
502: '应用程序错误',
503: '维护中',
504: '网关超时',
}
// 请求的默认参数
const defaultOptions = {
method: 'GET',
success: function(){},
fail: function(err){
console.log(err)
_showToast("网络错误")
},
complete: function(){},
}
// 默认的额外参数
const defaultOtherOptions = {
useSuccessCode: true, // 是否检测预定义的http请求的成功状态
isShowError: true, // 是否显示错误提醒
globalData: '',
}
// 返回Promise的微信请求对象,(wx.request返回的不是Promise)
function _wxRequest(options) {
return new Promise((resolve, reject) => {
try {
wx.request({
...options,
success: function(...arg) {
resolve(...arg)
},
fail: function(...arg) {
reject(...arg)
},
})
}catch (e) {
console.log(e)
reject(e)
}
})
}
// toast提醒函数
function _showToast(title = '网络请求失败', duration = 3000) {
wx.showToast({
title: title,
icon: 'none',
duration: duration
})
}
// 导出
export default async function(fetchOptions = {header: {}}, otherOptions) {
// 合并otherOptions
otherOptions = typeof otherOptions !== 'undefined' ? {...defaultOtherOptions, ...otherOptions} : defaultOtherOptions
// app.js中获取不到getApp(), globalData需要传进来
if(otherOptions.globalData) { // app.js传globalData
globalData = otherOptions.globalData
}else { // 非app.js不传
const app = getApp()
globalData = JSON.parse(JSON.stringify(app.globalData))
}
// 设置header统一的Api-Ext属性
defaultOptions.header = {'Api-Ext': globalData.apiExt}
// 合并newFetchOptions
const newHeader = {...defaultOptions.header, ...fetchOptions.header}
const newFetchOptions = {...defaultOptions, ...fetchOptions}
newFetchOptions.header = newHeader
newFetchOptions.url = `${globalData.http}${newFetchOptions.url}`
// 等异步wx.request()返回结果
const requestResult = await _wxRequest(newFetchOptions)
const { errMsg } = requestResult
// 真机wx.toast和showLoading只能同时允许一个出现,故请求完成全部关闭,以便后续能toast提醒
wx.hideLoading()
// 请求网络成功
if(errMsg.includes('ok')) {
// 使用预设的成功状态
if(otherOptions.useSuccessCode) {
const { statusCode, data } = requestResult
if( successCodes.findIndex((item) => item === statusCode) > -1) { // 成功code,返回requestResult.data和requestResult
newFetchOptions.success(data, requestResult)
}else { // 提醒错误,不返回requestResult
console.log('非请求成功状态', requestResult)
let msg = codeMessage[statusCode] || '未知错误'
if(typeof data !== 'undefined' && typeof data.message !== 'undefined') {
msg = data.message
}
otherOptions.isShowError && _showToast(msg)
}
}else{ // 不使用预设的成功状态, 返回整个requestResult
console.log('不使用预设的成功状态', requestResult)
newFetchOptions.success(requestResult)
}
} else { // 请求网络失败
otherOptions.isShowError && _showToast()
newFetchOptions.fail(requestResult)
}
// 执行complete函数
newFetchOptions.complete()
}
request.js的注意点:
- 接口domain,由于app.js中调用不到getApp()实例,就没有这样写:
const app = getApp()
const globalData = app.globalData.http
改成这样写:app.js中调用request,额外传入其他参数:globalData,其他js中直接调用getApp()取到:
// app.js中获取不到getApp(), globalData需要传进来
if(otherOptions.globalData) { // app.js传globalData
globalData = otherOptions.globalData
}else { // 非app.js不传
const app = getApp()
globalData = JSON.parse(JSON.stringify(app.globalData))
}
2. 等异步wx.request()返回结果,直接关闭wx.hideLoading();因为小程序中wx.showToast, wx.showLoading, 同时只能出现一个;wx.hideLoading()都会关闭2者的土司弹窗。也就不用每次request的时候都写complete来关闭弹窗:如:
complete() {
wx.hideLoading()
}
3. 设计其他参数:otherOptions.useSuccessCode的用意是:有的请求需要通过statusCode来处理不同的逻辑,就返回(response),正常成功请求的返回(data, response)(返回data是为了success中少写点代码:如:data = response.data; 返回response以防万一需要用到response)。
4. 函数开始得先合并请求参数newFetchOptions,把header中相同的“Api-Ext”写入,并把请求的url合并起来:newFetchOptions.url = `${globalData.http}${newFetchOptions.url}`。
5. wx.request请求是异步的,我们通过async/await来同步书写代码。把wx.request封装起来,返回Promise对象。并在函数内部把wx.request可能出现的错误捕获掉(其实这个try/catch可以不用捕获的,wx.request发生错误都捕获到fail()里了)。
function _wxRequest(options) {
// 由于wx.request不是返回Promise对象,我们要自己包一层Promise
return new Promise((resolve, reject) => {
try {
wx.request({
...options,
success: function(...arg) {
resolve(...arg)
},
fail: function(...arg) {
reject(...arg)
},
})
}catch (e) {
console.log(e)
reject(e)
}
})
}
const requestResult = await _wxRequest(newFetchOptions)
6. 非成功状态码的文本提醒,如果接口有返回提醒,则用接口的提醒,否则用messageCode中的提醒:
console.log('非请求成功状态', requestResult)
let msg = codeMessage[statusCode] || '未知错误'
if(typeof data !== 'undefined' && typeof data.message !== 'undefined') {
msg = data.message
}
otherOptions.isShowError && _showToast(msg)
调用request.js的写法:
import request from '@Utils/request'
// 写法1:http状态码:200处理data, 成功回调的返回success(data, res)
request({
url: '/mpa/cart/...',
success(data, res) {
that.setData({
cartNum: data
})
}
})
// 写法2:通过statusCode处理不同逻辑,传入额外参数:{ useSuccessCode: false },成功回调的返回success(res)
request({
url: '/mpa/goods/...',
data: {
keywords: keyName
},
success(res) { // 这里就返回res
var code = res.statusCode.toString()
if (code == 500) {
that.setData({
dataList: []
})
} else if (code.indexOf('40') > -1) {
var tip = res.data.message.toString()
wx.showToast({
title: tip,
icon: 'none',
duration: 1000
})
} else {
let tempArr = res.data
tempArr.map(item => {
let string = that.getHilightStrArray(item.name, keyName)
item.newStringArr = string
return item
})
that.setData({
dataList: tempArr,
keyName: keyName
})
}
}
}, { useSuccessCode: false })
// 写法3:在app.js中调用request,得额外传入globalData
request({
url: '/mpa/.../...',
success(data, res) {
that.globalData.distribution = data
resolve()
},
fail(res) {
reject(res)
}
}, { globalData: that.globalData })
相比之前的wx.request代码,简洁多了,而且请求一目了然,错误code都有统一提醒; 我们只需要刪除原有多余的代码就好,而不用去改原有代码的写法。做到更低代码量的重构改动。
总结:
重构思想:
- 能复用的要尽量复用,少写多余重复的代码。
- 要多多考虑到边界情况:如:otherOptions.isShowError是否要显示错误的toast弹窗;接口没有返回错误的message,let msg = codeMessage[statusCode] || '未知错误',补足msg;预留额外参数:otherOptions。等等...
- 要考虑到多种调用场景或情况。如:app.js中调用;success中通过statusCode执行不同逻辑。
- 代码顺序最好用es6后的新语法来同步书写异步代码,命名尽量语义化,多写备注,才能一目了然,毕竟你的代码后面也会有人接手的。如: if(errMsg.includes('ok'))一看就知道是请求成功的情况。
- 尽量封装好一些默认的行为,如:
function _showToast(title = '网络请求失败', duration = 3000) {
wx.showToast({
title: title,
icon: 'none',
duration: duration
})
}
const defaultOptions = {
method: 'GET',
success: function(){},
fail: function(err){
console.log(err)
_showToast("网络错误")
},
complete: function(){},
}
要想改动原有项目大量的代码,最好重构到不破坏原有代码的结构去精简代码(而且不是你自己的写项目,代码改多了,可能会出现很多意想不到的结果,反而得花更多的时间去排查错误),就这个request.js里面的坑还蛮多的;比如:
wx.request()返回的不是Promise对象;
app.js中获取不到getApp()实例,globalData就得额外传入;
真机wx.toast和showLoading只能同时允许一个出现,故请求完成全部关闭,以便后续能toast提醒;
小程序真机调试中:400,500等的httpCode都不会报异常,而且所有的异常都会阻塞代码进程,错误异常记得捕获。真机调试非常的头疼,可多写log输出。(调试时间花费了非常多...)
改这个也是头疼......