流程图

文件目录

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
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
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
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
import { get } from '@/request';

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