100 行代码打造小而美的 uni-app 请求库

一. 前言

在开发 uni-app 项目时,我们经常需要对接后端接口进行数据请求。虽然 uni-app 框架本身提供了uni.request 用于发起请求,但在实际项目中,我们往往会封装一些请求库来简化请求的操作,提高代码复用性和可维护性。

本文将介绍基于 uni.request 实现一款小而美的请求工具,通过大约 100 行代码的实现,为 uni-app 项目打造一个简洁高效的请求库。

二. 请求库的设计思路

1. 了解 uni.request

在 uni-app 中使用 uni.request 来发起请求,但这种直接调用 uni.request 的方式在实际开发中存在一些不足之处,比如请求逻辑过于分散、请求参数拼接繁琐等。因此,我们希望通过封装一个简单的请求库来优化这一过程。

在 uni-app 使用 uni.request 的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码uni.request({
// url 仅为示例,并非真实接口地址。
url: "https://www.example.com/request",
data: {
text: "uni.request",
},
header: {
// 自定义请求头信息
"custom-header": "hello",
},
success: (res) => {
console.log(res.data);
this.text = "request success";
},
});

以上的这种请求方式是在 uni-app 项目中最常见的代码书写方式,很简单也很好理解,但是无法支持 promise API 式的请求,不支持请求和响应拦截器,同时也不支持全局的变量配置,逻辑分散,不便于维护!

不过我最近看官方文档,官方已经对部分 API 进行了 Promise 封装

详情参考:官方 Promise - 封装

2. 请求库的实现目标

image.png

我的目标核心是实现一个基于 Promise 的,轻量且强大的 http 网络库,基于以上这个目标,我的请求库应该具备以下基本功能:

  1. 提供统一的 Promise API
  2. 基于 uni.request,支持多种运行环境,浏览器 H5、小程序、APP 等
  3. 支持发起 GETPOSTPUTDELETE 请求
  4. 支持请求/响应拦截器
  5. 支持设置请求的 header
  6. 支持处理请求的 loading 状态
  7. 支持对请求结果进行统一处理
  8. 支持链式调用

三. 请求库实现

基于以上的目标特性,我会一步一步实现请求库,而它的实现应该主要包括以下几个核心点:

  1. Config 配置项:设计一个统一的配置项对象 config,其中包含了 baseURL、header、data、method、dataType、responseType 等网络请求的基本配置信息。这样可以确保在发起网络请求时,可以统一管理这些配置项,方便进行全局设置和覆盖。
  2. 拦截器 Interceptors:设计请求拦截器和响应拦截器,提供 use 方法来添加拦截器处理函数。请求拦截器可以在发送请求前对请求参数进行处理,而响应拦截器则可以在收到响应后对响应结果进行处理。通过拦截器可以实现一些通用的网络请求处理逻辑,比如添加请求头、处理请求参数、统一处理响应结果等。
  3. 链式调用:在 setBaseURLsetHeadersetData 等方法中都使用了链式调用的方式,即每一个方法返回当前实例的引用,使得可以连续调用多个方法来设置请求的各个参数,提高代码的可读性和可维护性。
  4. Promise 处理:在 request 方法中使用了 Promise 对网络请求的结果进行处理,包括请求成功和失败的处理逻辑。同时也通过 Promise 来处理拦截器的返回结果,保证请求和拦截器的执行顺序和逻辑。
  5. 错误处理:在请求完成后,根据返回的状态码来判断请求的成功与失败,并通过不同的处理逻辑来处理不同状态下的响应结果。同时,在拦截器和请求的过程中,也会对错误进行处理,保证请求过程的稳定性和可靠性。

接下来我们分别对以上的核心点进行一一实现:

1. 构造函数

  • 在构造函数中,初始化 config 对象和 interceptors 对象,分别用来存储请求的配置信息和拦截器。
  • config 包含了 baseURL、header、data、method、dataType、responseType、success、fail 和 complete 等属性。
  • interceptors 包含了 request 和 response 拦截器,用来处理请求和响应的拦截操作。

通过构造函数,来定义统一的公共变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码class Http {
constructor() {
this.config = {
baseURL: "",
header: { "Content-Type": "application/json;charset=UTF-8" },
data: {},
method: "GET",
dataType: "json",
responseType: "text",
success() {},
fail() {},
complete() {},
};
}
}

2. 设置 BaseURL

定义 setBaseURL 方法,用来设置请求的基础 URL 统一请求前缀,将传入的 baseURL 参数赋值给 config.baseURL 属性。

1
2
3
4
js复制代码function setBaseURL(baseURL) {
this.config.baseURL = baseURL;
return this;
}

3. 设置请求 header

定义 setHeader 方法,用来设置请求的头部信息,将传入的 header 参数与原有的 header 属性合并更新。

1
2
3
4
js复制代码function setHeader(header) {
this.config.header = { ...this.config.header, ...header };
return this;
}

4. 设置请求体

定义 setData 方法,用来设置请求的数据,根据传入的数据类型判断是直接赋值还是合并更新到 config.data 属性中。

1
2
3
4
5
6
7
8
js复制代码function setData(data) {
if (Array.isArray(data)) {
this.config.data = data;
} else {
this.config.data = { ...this.config.data, ...data };
}
return this;
}

5. 设置拦截器

设计请求拦截器和响应拦截器,提供 use 方法来添加拦截器处理函数。请求拦截器可以在发送请求前对请求参数进行处理,而响应拦截器则可以在收到响应后对响应结果进行处理。

通过拦截器可以实现一些通用的网络请求处理逻辑,比如添加请求头、处理请求参数、统一处理响应结果等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码this.interceptors = {
response: {
use(handler, onerror, complete) {
this.handler = handler;
this.onerror = onerror;
this.complete = complete;
},
},
request: {
use(handler) {
this.handler = handler;
},
},
};

6. 基于 Promise 对象来实现

通过以上的配置,其实我们已经完成了一半,接下来,使用这些配置好的变量按需使用 uni.request 就可以了,是不是很简单?

而 request 方法应该包含以下内容:

  • request 方法用来发起请求,根据传入的 URL、数据和选项进行请求配置。
  • 先处理请求的基础配置,包括 URL、baseURL、header、method、dataType 等。
  • 接着处理拦截器,分为 request 和 response 拦截器,根据拦截器的设置进行相应的拦截操作。
  • 最后返回一个 Promise 对象,实现异步请求的链式调用,并根据请求结果执行相应的回调处理。

接下来,我们按部就班的来实现我们最重要的 request 方法

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
js复制代码function request(url, data, options) {
if (!options) options = {};
// 请求URL
options.url = url;
// 请求baseURL:优先级为:实时传递的 > 公共配置的
options.baseURL =
options.baseURL !== undefined ? options.baseURL : this.config.baseURL;
// 请求头:合并公共配置与实时设置的header, 且优先级实时设置会覆盖公共配置的
options.header = { ...this.config.header, ...options.header };
// 请求方式:优先级为:实时传递的 > 公共配置的
options.method = options.method || this.config.method;
// 数据格式:默认json
options.dataType = options.dataType || this.config.dataType;
// 请求体:优先级为:实时传递的 > 公共配置的
if (isArray(data)) {
options.data = data;
} else {
options.data = { ...this.config.data, ...data };
}
// 拦截器处理
let interceptors = this.interceptors;
let requestInterceptor = interceptors.request;
let responseInterceptor = interceptors.response;
let requestInterceptorHandler = requestInterceptor.handler;
// 实现 Promise
return new Promise((resolve, reject) => {
function isPromise(p) {
return p && p.then && p.catch;
}

/**
* 公用方法
* If the request/response interceptor has been locked
*/
function enqueueIfLocked(promise, callback) {
if (promise) {
promise.then(() => {
callback();
});
} else {
callback();
}
}
// 响应回调
function onresult(handler, response, type) {
enqueueIfLocked(responseInterceptor.p, function () {
if (handler) {
// 统一添加请求信息
response.request = options;
let ret = handler.call(responseInterceptor, response, Promise);
response = ret === undefined ? response : ret;
}
if (!isPromise(response)) {
response = Promise[type === 0 ? "resolve" : "reject"](response);
}
response
.then((d) => {
resolve(d.data);
})
.catch((e) => {
reject(e);
});
});
}
// 请求完成回调,不管请求成功或者失败,都会走这个方法
options.complete = (response) => {
let statusCode = response.statusCode;
let type = 0;
if ((statusCode >= 200 && statusCode < 300) || statusCode === 304) {
// 请求成功
type = 0;
onresult(responseInterceptor.handler, response, type);
} else {
// 请求错误
type = -1;
onresult(responseInterceptor.onerror, response, type);
}
// 请求完成,无论请求成功、失败都会走的回调
onresult(responseInterceptor.complete, response, type);
};

// 开始请求
enqueueIfLocked(requestInterceptor.p, () => {
options = Object.assign({}, this.config, options);
options.requestId = new Date().getTime();
let ret = options;
if (requestInterceptorHandler) {
ret =
requestInterceptorHandler.call(
requestInterceptor,
options,
Promise
) || options;
}
if (!isPromise(ret)) {
ret = Promise.resolve(ret);
}
ret.then(
(d) => {
if (d === options) {
// url处理
d.url =
d.url && d.url.indexOf("http") !== 0 ? d.baseURL + d.url : d.url;
// 是否有追加restful url
d.url = d.restURL ? d.url + d.restURL : d.url;
// 使用 uni.request 正式请求, d 为所有的请求参数
uni.request(d);
} else {
resolve(d);
}
},
(err) => {
reject(err);
}
);
});
});
}

综上所述,以上请求库的设计思路是以封装、拓展性和易用性为核心,通过配置项、拦截器、链式调用和 Promise 处理等设计,实现了一个简单但功能完善的网络请求库,适用于处理各种类型的网络请求需求。

总的来说,以上代码实现了一个简洁易用的 HTTP 请求类,结合了配置管理、拦截器功能和异步请求处理,并提供了一些常用的方法来方便进行 HTTP 请求的管理和处理。

四. 使用方式

1. npm 安装

我已经将uni-http的请求库发到了npm上了,并且在多个项目中得到了良好的应用,可直接使用 npm 安装使用,通过如下方式进行安装:

1
2
3
4
5
bash复制代码// 安装
npm install @anyup/uni-http -S

// 更新
npm update @anyup/uni-http

点击查看在 npm 上完整的 uni-http 请求库,欢迎大家使用:npm 地址

2. 快速上手

实例化 Http 类

Http 为一个类,所以在使用前需要实例化 new,通过构造函数实例化一些必备参数

1
2
3
4
5
6
7
8
9
10
11
12
js复制代码import { Http } from "@anyup/uni-http";

const http = new Http();

// 设置统一的头部
const header = { "Content-Type": "application/json" };

// 仅为示例 API 域名,使用时替换自己的接口域名即可
const baseURL = "https://demo.api.com";

// 设置 baseURL 和 header,支持链式调用
http.setBaseURL(baseURL).setHeader(header);

设置拦截器

  • 通过 interceptors.request.use 设置请求拦截器,主要对请求 header 的配置,比如 token 等。
  • 通过 interceptors.response.use 设置响应拦截器,如果需要,可以对所有的请求成功的响应数据做统一的业务处理,以简化代码。
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
js复制代码// 设置请求拦截器
http.interceptors.request.use(
(request) => {
if (request.loading) {
// 如果配置了loading,请求开始需要显示
}
// 设置请求header
request.header["Authorization"] = "";
return request;
},
(error) => Promise.resolve(error)
);
// 设置响应拦截器
http.interceptors.response.use(
(response) => {
// 请求成功
if (!response.data) {
return Promise.reject(new Error("接口请求未知错误"));
}
// 其他业务处理
return Promise.resolve(response);
},
(error) => {
// 请求失败,业务处理
return Promise.reject(error);
},
(complete) => {
// 请求完成
if (complete.request.loading) {
// 如果配置了loading,请求完成需要关闭
}
// 其他业务处理
console.log("complete", complete);
}
);

请求示例

以登录请求为示例,通过传递 url,data,option,分别配置请求的 api 地址,请求参数,以及请求的个性化配置(是否需要请求 loading 等)。

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
js复制代码// 登录请求示例,并配置请求时显示loading
function requestLogin(username, password) {
http
.request(
"/login",
{ username, password },
{ method: "POST", loading: true }
)
.then((res) => {
// 处理 response 响应
if (res.status === 1) {
console.log(res);
}
});
}

// 直接使用 get|post|put|delete 方式请求
function requestLogin1(username, password) {
// 也可以直接使用 post 方法
http.post("/login", { username, password }, { loading: true }).then((res) => {
// 处理 response 响应
if (res.status === 1) {
console.log(res);
}
});
}

// es6 await 风格 登录请求示例
async function requestLogin2(username, password) {
const res = await http.request(
"/login",
{ username, password },
{ method: "POST", loading: true }
);
// 处理 response 响应
if (res.status === 1) {
console.log(res);
}
}

五. 总结

通过以上代码的实现,我们成功构建了一个简洁高效的 uni-app 请求库,实现了一个小而美的 uni-app 请求库,能够满足常见的请求需求,同时除去注释,代码量也被控制在了 100 行左右。而且还实现了统一请求响应拦截器、全局错误处理等功能,使用它可以让我们的项目开发变得更加顺畅和便利。

希望本文能够对大家在 uni-app 项目中封装请求库有所帮助,让我们的开发工作更加高效和便捷。

在实际项目中,如果你有任何疑问或建议,欢迎联系我,我可以根据需求继续扩展这个请求库。

六. 结语

  • 如果你有任何问题,或者想要共同学习交流,欢迎通过沸点联系我:点击查看沸点
  • 剧透:篇幅有限,下篇文章我会继续以此为基础,说明如何实现批量生成 API 请求,简化代码量 99.99%,通过类的工厂模式,搞定繁琐的请求 function 定义,敬请关注!

源码

开源不易,欢迎 Start、Fork

本文正在参加金石计划征文活动,欢迎点赞、收藏加关注,欢迎在评论区留言,让我们一起讨论进步!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%