鸿蒙开发--基于axios封装网络请求实现链式调用

比较喜欢链式调用的方式,需要设置什么参数,调用具体的方法,视觉和逻辑上简洁明了。实现一个基于axios的网络请求封装,实现链式调用的效果

1、使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  //调用方式
HttpHelper.newClient<DataBean>()
.host("https://www.wanandroid.com")//可以全局定义域名,也可以后续传入,可用于多域名的情况
.api("article/list/0/json")//接口路径
.enableShowLoading(true)//是否显示加载弹窗
.enableShowToast(true)//是否toast显示请求错误信息
.params(Object({ "cid": 60 }))//传入参数
.onSuccess((info) => {
//成功回调,如果不需要可以不写
data(info)
})
.onError((error) => {
//错误回调,如果不需要可以不写
})
.get()//.post() .delete() put()

//使用中一般只需要这样,
class TestViewModel {
getListData2(data: (data?: DataBean) => void) {
HttpHelper.newClient<DataBean>()
.api("article/list/0/json")
.params(Object({ "cid": 60 }))
.onSuccess((info) => {
data(info)
})
.get()////.post() .delete() put()
}
}

2、实现方法

定义后端返回的数据格式,按需定义

1
2
3
4
5
6
7
8
9
10
11
12
//服务端返回的数据格式
export interface BaseApiResponse<T> {
errorCode?: number|string
errorMsg?: string
data?: T
}

//封装一个错误信息对象
export interface BaseError {
message: string;
code?: number | string;
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/**
* 链式调用实现网络请求
*/
export class HttpHelper<T> {
private baseUrl: string = "https://www.wanandroid.com" //请求域名,默认放置
private timeOut: number = 10000 //超时时间

private requestApi?: string //接口路径
private paramsData?: object //参数


private isShowLoadingDialog: boolean = true//是否显示加载中弹窗
private isShowErrorToast: boolean = true//是否自动显示错误信息
private axiosInstance?: AxiosInstance; // axios 实例


private success?: (result?: T) => void;//请求成功返回正确数据的回调
private error?: (result?: BaseError) => void;//请求失败的回调

//链式调用,禁掉手动new对象
private constructor() {
}


// axios 请求配置
private defaultConfig: AxiosRequestConfig = {
baseURL: this.baseUrl,
timeout: this.timeOut
}

/**
* 动态设置域名
* @param host
* @returns
*/
host(host: string): HttpHelper<T> {
this.baseUrl = host
return this;
}


/**
* 接口路径
* @param api
* @returns
*/
api(api?: string): HttpHelper<T> {
this.requestApi = api
return this
}

/**
* 请求参数
* @param params
* @returns
*/
params(params?: object): HttpHelper<T> {
this.paramsData = params
return this
}


/**
* 创建对象
* @returns
*/
public static newClient<T>(): HttpHelper<T> {
return new HttpHelper()
}

/**
* 外部传入axios 配置,好像用不到,后续如果需要配置,仍是使用封装类增加相关配置
* @param config
* @returns
*/
// config(config: AxiosRequestConfig): HttpHelper<T> {
// this.defaultConfig = config
// return this
// }


/**
* 使用get方法
*/
get() {
this.request(RequestType.GET)
}
/**
* 使用post方法
*/
post() {
this.request(RequestType.POST)
}
/**
* 使用put方法
*/
put() {
this.request(RequestType.PUT)
}
/**
* 使用delete方法
*/
delete() {
this.request(RequestType.DELETE)
}


/**
* 传入成功回调函数
* @param success
* @returns
*/
onSuccess(success?: (result?: T) => void): HttpHelper<T> {
this.success = success;
return this;
}

/**
* 传入请求错误回调
* @param error
* @returns
*/
onError(error?: (result?: BaseError) => void): HttpHelper<T> {
this.error = error;
return this;
}

/**
* 设置是否允许显示加载中弹窗
* @param enableLoading
* @returns
*/
enableShowLoading(enableLoading: boolean): HttpHelper<T> {
this.isShowLoadingDialog = enableLoading;
return this;
}
/**
* 设置是否允许显示错误信息toast
* @param enableLoading
* @returns
*/
enableShowToast(enable: boolean): HttpHelper<T> {
this.isShowErrorToast = enable
return this
}


/**
* 在发起请求方法前,更新下配置信息
*/
private updateBaseConfig() {
this.defaultConfig.baseURL = this.baseUrl
this.defaultConfig.timeout = this.timeOut

this.axiosInstance = axios.create(this.defaultConfig);
this.initIntercept()
}


/**
* 设置拦截器信息,需要添加token,可以在这边全局设置
*/
private initIntercept() {
// 添加请求拦截器
this.axiosInstance?.interceptors.request.use((config: InternalAxiosRequestConfig) => {
let info = `请求路径 ${config.baseURL}${config.url}\n请求参数 ${JSON.stringify(config.params)}`
LogUtil.info(info)
return config;
}, (error: AxiosError) => {
return Promise.reject(error);
});
}


private showLoading() {
if (this.isShowLoadingDialog) {
LoadingDialog.getInstance().show()
}
}

private hideLoading() {
if (this.isShowLoadingDialog) {
LoadingDialog.getInstance().hide()
}
}

private showToast(message?: string) {
if (this.isShowErrorToast && message) {
ToastUtil.showShort(message)
}
}


/**
* 请求接口具体实现
* @param type
*/
private request(type: RequestType) {

this.updateBaseConfig()
this.showLoading()
let response: Promise<BaseResponse<BaseApiResponse<T>>>

switch (type) {
case RequestType.GET:
response = this.axiosInstance!.get<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
case RequestType.POST:
response = this.axiosInstance!.post<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
case RequestType.PUT:
response = this.axiosInstance!.put<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
case RequestType.DELETE:
response = this.axiosInstance!.delete<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break

default:
response = this.axiosInstance!.get<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
}

response.then((res) => {
let data = res.data
//获取到的数据无法像java那样直接映射成具体的数据对象类型,就算强转类型也无法使用对象中的方法
if (data.errorCode == 0) {
//请求成功获取到预期数据
if (this.success) {
this.success(data.data)
}
} else {
//请求成功,但是是返回服务器错误信息
if (this.error) {
let errBean: BaseError = {
code: data.errorCode,
message: data.errorMsg ?? ""
}
this.showToast(errBean.message)
if (this.error) {
this.error(errBean)
}
}
}
}).catch((error: AxiosError) => {
//请求失败,如网络无法连接
let errBean: BaseError = {
code: error.code,
message: error.message
}

this.showToast(errBean.message)

if (this.error) {
this.error(errBean)
}
}).finally(() => {
this.hideLoading()

});
}
}

3、问题

当后端返回的数据类型不统一时可能无法正常使用,比如:

1
2
3
4
5
6
7
8
9
10
11
{
code:1
message:""
data:{...}
}

{
errorCode:1
errorMsg:""
datas:{...}
}

封装实现了分割请求成功和失败的两种情况,避免了各个接口请求的公共逻辑代码(如弹窗、错误信息显示),对于拿到手的数据可以更专注于请求成功后的逻辑处理,但是这样请求的数据格式在框架中就限制死了

1
let response: Promise<BaseResponse<BaseApiResponse<T>>>

如果拿到了不是这个格式的数据,那么后续的逻辑处理都可能出现问题。

尝试过把BaseApiResponse整个当做HttpHelper的泛型传入,打算通过封装BaseApiResponse的子类来解决格式不统一的问题,但是,由于语言限制,好像鸿蒙中无法像android中json和对象互相映射的功能,android中可以创建多种数据格式的子类,利用多态特性,关系映射解决掉这个问题,但是鸿蒙好像还不行

华为官方关于Json解析成对象的相关说明 ,使用第三方映射库还是得传入实际的对象类型,暂时是想不到怎么在底层拿到上层的数据类型。

暂时没有比较完美的解决方式,本地端能处理的估计就是在拿到数据后如果已知数据格式不统一,在拦截器中修改成需要的数据类型。

不知道有没有大佬看到,希望不吝赐教。