流程图

文件目录

1
2
3
4
⊢ request
⨽ http.ts
⨽ index.ts
⨽ interceptors.ts

使用 Taro.addInterceptor 添加请求/响应拦截器 [interceptors.ts]

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
import Taro from '@tarojs/taro';
import { post } from '.';

/**
* 获取 Token
*/
export async function requestToken() {
let token = Taro.getStorageSync('userToken') || '';

if (!token) {
const appid = Taro.getAccountInfoSync().miniProgram.appId;
const { code } = await Taro.login();

// 后台换取 Token 的接口
const {
data: { data },
} = await post(
`user/wxLogin?code=${code}&appId=${appid}`,
{},
{ isNeedToken: false }
);

Taro.setStorageSync('userToken', data);
token = data;
}

/**
* 如果其他页面通过 Taros.getStorageSync("userToken") 获取不到 token, Taro.setStorageSync() 存储需要一定的时间
* 可以使用事件监听的方式获取 token, eg: Taro.eventCenter.trigger("userToken", token)
*/

return token;
}

/**
* 请求拦截器
*/
async function requestInterceptor(request: Taro.RequestParams) {
const { header, isNeedToken, isShowLoading } = request;

if (isShowLoading) Taro.showLoading({ title: '加载中', mask: true });

if (isNeedToken)
request.header = { ...header, Authorization: await requestToken() };

return request;
}

/**
* 响应拦截器
*/
function responseInterceptor(
request: Taro.RequestParams,
response: Taro.request.SuccessCallbackResult
) {
const { isShowLoading, isShowFailToast, isThrowError } = request;
const { statusCode, data, errMsg } = response; // HTTP 返回的数据格式

isShowLoading && Taro.hideLoading();

if (statusCode === 200) {
// HTTP 成功
const { code, msg } = data; // 后端自定义的响应格式

if (code == 0) {
// 后端返回的 code == 0 代表请求成功
return response;
} else {
if (isShowFailToast)
Taro.showToast({
icon: 'none',
title: msg || '未知错误,十分抱歉!',
duration: 2000,
mask: true,
});

if (isThrowError) throw new Error(`后端返回的错误信息-- ${msg}`); // 抛出错误, 阻止程序向下执行

return response; // 程序继续往下走
}
} else {
// HTTP 失败
let title = '未知错误,万分抱歉!';

if (statusCode === -1) title = '网络请求失败,请检查您的网络。';

if (statusCode > 0)
title = `url:${request.url.toString()}, statusCode:${
response.statusCode
}`;

if (statusCode == 401) {
Taro.clearStorage();
Taro.reLaunch({ url: '/pages/home/index' });
}

if (isShowFailToast)
Taro.showToast({
icon: 'none',
title: title || errMsg,
duration: 2000,
mask: true,
});

throw new Error(`HTTP请求失败---- ${title || errMsg}`); // 抛出错误, 阻止程序向下执行
}
}

/**
* 参考官方文档
* https://taro-docs.jd.com/docs/apis/network/request/addInterceptor
*/
const interceptor = async function (chain: Taro.Chain) {
const requestParams = chain.requestParams;
// const { method, data, url } = requestParams;

// console.log(`http ${method || "GET"} --> ${url} data: `, data);

let req = await requestInterceptor(requestParams); // 请求拦截器

return chain.proceed(req).then((res: Taro.request.SuccessCallbackResult) => {
// console.log(`http <-- ${url} result:`, res);

return responseInterceptor(req, res); // 响应拦截器
});
};

export default interceptor;

使用 Taro.request 封装请求 [http.ts]

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
import Taro from '@tarojs/taro';
import interceptor from './interceptors';

const baseUrl = 'http://127.0.0.1:8081' + '/api';

// Taro 提供了两个内置拦截器
// logInterceptor - 用于打印请求的相关信息
// timeoutInterceptor - 在请求超时时抛出错误。
// const interceptors = [customInterceptor, Taro.interceptors.logInterceptor]

/**
* 添加拦截器 (务必与 Taro.request 放到一起)
* https://taro-docs.jd.com/docs/apis/network/request/addInterceptor
*/
Taro.addInterceptor(interceptor);

/** 自定义请求体 */
export interface IRequestData {
[key: string]: any;
}

/**
* 自定义响应体 (根据后端的返回的 数据格式 来)
* 或者: Taro.request.SuccessCallbackResult<T>
*/
export interface IResponseData<T> {
msg: string;
data: T;
code: number;
total: number;
}

/** 自定义配置 */
export interface IRequestOption
extends Partial<Taro.request.Option<string | IRequestData>> {
/**
* 是否需要Token
* @default true
*/
isNeedToken?: boolean;

/**
* 是否显示Loading遮罩层
* @default false
*/
isShowLoading?: boolean;

/**
* 是否显示失败Toast弹框
* @default false
*/
isShowFailToast?: boolean;

/**
* 是否抛出错误 (阻止代码的继续运行)
* @default false
*/
isThrowError?: boolean;
}

/** 封装请求类 */
class HttpRequest {
customOptions: IRequestOption = {
isNeedToken: true,
isShowLoading: false,
isShowFailToast: false,
isThrowError: false,
};

async request<T>(
url: string,
data: string | IRequestData = {},
options: IRequestOption
): Promise<Taro.request.SuccessCallbackResult<IResponseData<T>>> {
const requestUrl = this.normalizationUrl(url);
const header = { 'Content-Type': 'application/json' };
const requestData = data;
const requestOptions = { ...this.customOptions, ...options };
const params = {
url: requestUrl,
header,
data: requestData,
...requestOptions,
};

const resp: any = await Taro.request(params); // 发起请求
return resp;
}

/** 处理 url */
private normalizationUrl(url: string) {
let requestUrl = url;

if (baseUrl[baseUrl.length - 1] === '/') {
// 判断 baseUrl 最后是否有 '/'
requestUrl[0] === '/' && (requestUrl = requestUrl.replace('/', '')); // 去除 requestUrl 最前面的 '/'
} else {
requestUrl[0] !== '/' && (requestUrl = '/' + requestUrl); // 给 requestUrl 最前面加上 '/'
}

if (!/^https{0,1}:\/\//g.test(requestUrl)) {
// 判断 requestUrl 是否是 http:// 或 https:// 开头
requestUrl = `${baseUrl}${requestUrl}`;
}

return requestUrl;
}
}

const http = new HttpRequest();
export default http;

抛出 get/post/put/del 方法 [index.ts]

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
import http, { IRequestData, IRequestOption } from './http';

export function get<T = any>(
url: string,
data?: string | IRequestData,
option: IRequestOption = {}
) {
return http.request<T>(url, data, { method: 'GET', ...option });
}

export function post<T = any>(
url: string,
data?: string | IRequestData,
option: IRequestOption = {}
) {
return http.request<T>(url, data, { method: 'POST', ...option });
}

export function put<T = any>(
url: string,
data?: string | IRequestData,
option: IRequestOption = {}
) {
return http.request<T>(url, data, { method: 'PUT', ...option });
}

export function del<T = any>(
url: string,
data?: string | IRequestData,
option: IRequestOption = {}
) {
return http.request<T>(url, data, { method: 'DELETE', ...option });
}

export default http;

请求使用示例

1
2
3
4
import { get } from '@/request';

export const getUserInfoApi = (data: any) =>
get('/user', { ...data }, { isNeedToken: false, isShowFailToast: true });