介绍说明:
这是一个为 UniApp 框架设计的 增强型 HTTP 请求封装模块 (基于 uni.request
),使用 TypeScript 编写。它的核心目标是简化网络请求的处理,并自动、优雅地处理后端服务临时不可用 (HTTP 503) 的情况,从而提高应用的健壮性和用户体验。
当你调用后端接口时,偶尔可能会遇到服务器因为维护、过载或其他临时性问题而返回 503 状态码。通常,这意味着稍后重试请求可能会成功。本模块会自动捕获 503 错误,并根据预设(或自定义)的次数和延迟时间进行自动重试,避免了在每个请求中手动编写重试逻辑的繁琐工作。
主要特性:
- 🚀 503 自动重试 : 无需手动干预,自动处理
503 Service Unavailable
错误,并在指定延迟后重试。 - 🔧 配置灵活: 可以全局设置默认的重试次数和延迟时间,也可以在单次请求中覆盖这些配置。
- ✨ Promise 化 : 完全基于 Promise,完美支持
async/await
语法,让异步代码更清晰。 - 💡 辅助函数 : 提供
httpGet
,httpPost
,httpPut
,httpDelete
等常用方法的快捷方式,简化调用。 - 🛡️ 结构化错误处理 : 失败时
reject
一个包含详细信息的HttpRequestError
对象(包括消息、原始错误、状态码、是否网络错误等),便于精细化错误处理。 - 💬 可控错误提示 : 请求失败时默认弹出
uni.showToast
提示,但可通过选项 (hideErrorToast: true
) 禁用,方便自定义UI反馈。 - 🔒 类型安全 : 使用 TypeScript 泛型
<T>
,允许你指定期望的响应数据类型,在编译时提供更好的类型检查。 - 🧩 独立纯净: 不依赖外部状态管理库(如 Pinia/Vuex),易于集成到任何 UniApp 项目中。
适用场景:
- 任何需要与后端 API 交互的 UniApp 项目。
- 希望提高应用对后端临时故障容错能力的应用。
- 寻求更简洁、统一的网络请求处理方式的开发者。
如何使用:
将此模块(例如 request.ts
)放置在你的项目工具(utils)目录下,然后在需要发起网络请求的页面或组件中导入 http
或其辅助函数 (httpGet
, httpPost
等) 进行调用即可。
typescript
// 示例:
import { http } from '@/utils/request'; // 假设你放在 utils 目录下
async function fetchData() {
try {
const data = await http.get<MyDataType>('/api/data');
console.log(data);
} catch (error) {
console.error('请求失败:', error);
// 处理错误...
}
}
完整代码:
typescript
// src/utils/request.ts (或其他你喜欢的文件路径)
// --- 配置常量 ---
const DEFAULT_MAX_RETRIES = 2; // 默认最大重试次数 (总尝试次数 = 1 + 重试次数)
const DEFAULT_RETRY_DELAY = 1000; // 默认两次重试之间的延迟时间 (毫秒)
// --- TypeScript 接口定义 ---
/**
* 扩展 UniApp 的 RequestOptions,增加自定义的重试配置
*/
interface RetryRequestOptions extends UniApp.RequestOptions {
/** 针对 503 错误的最大重试次数 (默认: 2) */
retries?: number;
/** 每次重试之间的延迟时间,单位毫秒 (默认: 1000) */
retryDelay?: number;
/** 设置为 true 可隐藏自动弹出的错误提示 Toast (默认: false) */
hideErrorToast?: boolean;
}
/**
* 定义一个结构化的 HTTP 请求错误对象,用于 Promise 的 reject
*/
interface HttpRequestError {
/** 错误消息文本 */
message: string;
/** uni-app 返回的原始错误对象或请求失败的响应对象 */
originalError: UniApp.GeneralCallbackResult | UniApp.RequestSuccessCallbackResult;
/** 标志是否为网络连接错误 (即 fail 回调触发) */
isNetworkError?: boolean;
/** HTTP 状态码 (如果是非 2xx 的成功响应) */
statusCode?: number;
}
// --- 核心 HTTP 请求函数 ---
/**
* 发起一个 HTTP 请求 (基于 uni.request),并带有 503 自动重试逻辑。
*
* @template T - 期望的响应数据 `data` 的类型,默认为 `any`。
* @param {RetryRequestOptions} options - 请求配置,包含 uni.request 的标准选项以及自定义的重试选项。
* @returns {Promise<T>} - 返回一个 Promise。成功时 resolve 响应的 `data` 部分;失败时 reject 一个 `HttpRequestError` 对象。
*/
export const http = <T = any>(options: RetryRequestOptions): Promise<T> => {
// 解构选项,分离出重试配置和标准的 uni.request 配置
const {
retries = DEFAULT_MAX_RETRIES, // 获取重试次数,若未提供则使用默认值
retryDelay = DEFAULT_RETRY_DELAY, // 获取重试延迟,若未提供则使用默认值
hideErrorToast = false, // 获取是否隐藏错误提示的标志
...requestOptions // 剩余的选项作为 uni.request 的标准参数
} = options;
/**
* 内部函数,用于执行单次请求尝试
* @param currentAttempt - 当前是第几次尝试 (从 0 开始)
*/
const attemptRequest = (currentAttempt: number): Promise<T> => {
// 返回一个新的 Promise 来包装单次 uni.request 调用
return new Promise<T>((resolve, reject) => {
uni.request({
...requestOptions, // 传入标准的 uni.request 选项 (url, method, data, header 等)
// 请求成功的回调 (HTTP 状态码不一定是 2xx)
success(res) {
// --- 成功情况 (状态码 200-299) ---
if (res.statusCode >= 200 && res.statusCode < 300) {
// 假设服务器返回的有效数据在 res.data 中
resolve(res.data as T); // 使用 T 类型断言
return; // 成功,退出回调
}
// --- 需要重试的情况 (状态码 503 且 未达到最大重试次数) ---
if (res.statusCode === 503 && currentAttempt < retries) {
const attemptNum = currentAttempt + 1; // 计算下一次尝试的序号
console.warn(
`[HTTP] 请求失败,状态码 503。将在 ${retryDelay}ms 后进行第 ${attemptNum}/${retries} 次重试... URL: ${requestOptions.url}`
);
// 设置延迟执行下一次尝试
setTimeout(() => {
// 递归调用 attemptRequest,并将结果通过 then/catch 传递给外层 Promise
attemptRequest(attemptNum).then(resolve).catch(reject);
}, retryDelay);
return; // 等待重试,退出当前回调
}
// --- 其他失败情况 (非 2xx 状态码,或 503 重试次数已用尽) ---
// 构造错误消息
const errorMessage = `请求失败 [状态码: ${res.statusCode}]`;
console.error(`[HTTP] 请求错误 ${res.statusCode}: ${requestOptions.url}`, res);
// 如果用户没有选择隐藏提示,则显示 Toast
if (!hideErrorToast) {
uni.showToast({
icon: 'none',
// 尝试从响应体中获取更具体的错误信息,否则使用通用信息
title: (res.data as any)?.message || errorMessage,
duration: 2000,
});
}
// 用结构化的错误对象 reject Promise
reject({
message: errorMessage,
originalError: res, // 保留原始响应对象
statusCode: res.statusCode, // 记录状态码
} as HttpRequestError);
},
// 请求失败的回调 (网络层面的错误,例如 DNS 解析失败、网络不可达等)
fail(err) {
// --- 网络错误情况 ---
const errorMessage = '网络连接错误,请检查网络设置或稍后重试';
console.error(`[HTTP] 网络错误: ${requestOptions.url}`, err);
// 如果用户没有选择隐藏提示,则显示 Toast
if (!hideErrorToast) {
uni.showToast({
icon: 'none',
title: errorMessage,
duration: 2000,
});
}
// 用结构化的错误对象 reject Promise
reject({
message: errorMessage,
originalError: err, // 保留原始错误对象
isNetworkError: true, // 标记为网络错误
} as HttpRequestError);
},
// complete 回调在 success 或 fail 后都会执行,此处 Promise 的 resolve/reject 已处理最终状态,故无需操作
// complete() { }
});
});
};
// 发起第一次请求尝试 (currentAttempt = 0)
return attemptRequest(0);
};
// --- 便捷的辅助函数 ---
/** 定义辅助函数的选项类型,排除掉 url, method, data 这三个由辅助函数自身处理的参数 */
type HttpHelperOptions = Omit<RetryRequestOptions, 'url' | 'method' | 'data'>;
/**
* 发起 GET 请求 (带 503 重试)
* @template T - 期望的响应数据类型
* @param url - 请求地址
* @param data - 查询参数 (uni.request 中 GET 请求的 query 参数通常放在 data 对象里)
* @param options - 其他配置,如 headers, retries, hideErrorToast 等
*/
export const httpGet = <T = any>(
url: string,
data?: UniApp.RequestOptions['data'],
options?: HttpHelperOptions
): Promise<T> => {
return http<T>({
url,
data, // 对于 GET, uni.request 将 data 作为 query 参数附加到 URL
method: 'GET',
...options, // 合并其他选项
});
};
/**
* 发起 POST 请求 (带 503 重试)
* @template T - 期望的响应数据类型
* @param url - 请求地址
* @param data - 请求体数据
* @param options - 其他配置
*/
export const httpPost = <T = any>(
url: string,
data?: UniApp.RequestOptions['data'],
options?: HttpHelperOptions
): Promise<T> => {
return http<T>({
url,
data, // 请求体
method: 'POST',
...options,
});
};
/**
* 发起 PUT 请求 (带 503 重试)
* @template T - 期望的响应数据类型
* @param url - 请求地址
* @param data - 请求体数据
* @param options - 其他配置
*/
export const httpPut = <T = any>(
url: string,
data?: UniApp.RequestOptions['data'],
options?: HttpHelperOptions
): Promise<T> => {
return http<T>({
url,
data, // 请求体
method: 'PUT',
...options,
});
};
/**
* 发起 DELETE 请求 (带 503 重试)
* @template T - 期望的响应数据类型
* @param url - 请求地址
* @param data - 查询参数或请求体 (根据后端 API 设计,DELETE 有时也用 data 传递参数)
* @param options - 其他配置
*/
export const httpDelete = <T = any>(
url: string,
data?: UniApp.RequestOptions['data'],
options?: HttpHelperOptions
): Promise<T> => {
return http<T>({
url,
data,
method: 'DELETE',
...options,
});
};
// (可选) 将辅助函数附加到主 http 函数上,提供类似 axios 的调用风格
http.get = httpGet;
http.post = httpPost;
http.put = httpPut;
http.delete = httpDelete;
// --- 使用示例 ---
/*
// 定义一个接口来描述期望获取的数据结构
interface UserInfo {
id: number;
name: string;
email?: string;
}
async function fetchUserData() {
console.log('开始获取用户数据...');
try {
// 使用 http.get,并指定泛型类型为 UserInfo
// 第二个参数是 GET 请求的查询参数
// 第三个参数可以传递额外的配置,比如自定义请求头
const userInfo = await http.get<UserInfo>(
'/api/user/profile', // 假设的 API 地址
{ userId: 123 }, // 查询参数: /api/user/profile?userId=123
{
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
// retries: 1, // 可以覆盖默认的重试次数
// hideErrorToast: true, // 可以在单次调用中禁用 Toast
}
);
console.log('成功获取用户数据:', userInfo);
// 可以安全地访问 userInfo.id, userInfo.name 等属性
} catch (error: any) {
// 捕获错误,类型断言为我们定义的 HttpRequestError
const err = error as HttpRequestError;
console.error('获取用户数据失败:', err.message); // 输出友好的错误信息
// 可以根据错误类型做进一步处理
if (err.isNetworkError) {
console.log('失败原因:网络连接问题。');
} else {
console.log(`失败原因:服务器返回状态码 ${err.statusCode}。`);
// 可以查看原始错误对象获取更多细节
// console.log('原始错误详情:', err.originalError);
}
}
}
async function updateUserProfile() {
console.log('开始更新用户资料...');
try {
// 使用 http.post 发送数据
const result = await http.post<{ success: boolean; message: string }>(
'/api/user/update', // 假设的更新 API
{ name: '新名字', email: '[email protected]' }, // 请求体数据
{ headers: { 'Authorization': 'Bearer YOUR_TOKEN' } }
);
console.log('更新结果:', result); // 查看服务器返回的结果
} catch (error: any) {
const err = error as HttpRequestError;
console.error('更新用户资料失败:', err.message);
// 处理更新失败的情况...
}
}
// 调用示例函数
fetchUserData();
// updateUserProfile();
*/
-
核心
http
函数:- 封装了 UniApp 的
uni.request
API。 - 内置 503 重试 : 当请求收到
503 Service Unavailable
状态码时,会自动进行重试。 - 可配置 : 你可以通过
options
对象自定义重试次数 (retries
) 和重试间的延迟时间 (retryDelay
)。 - Promise 化 : 返回 Promise,方便使用
async/await
或.then()/.catch()
处理异步操作。 - 错误处理 : 网络错误或最终失败(包括重试用尽后的 503 或其他 HTTP 错误)会
reject
一个包含详细信息的HttpRequestError
对象。 - 错误提示 : 默认情况下,请求失败时会弹出
uni.showToast
提示,但可以通过hideErrorToast: true
选项禁用。 - 泛型支持 : 使用
<T>
泛型,允许你指定期望的响应数据data
的类型,增强代码类型安全。
- 封装了 UniApp 的
-
辅助函数 (
httpGet
,httpPost
,httpPut
,httpDelete
):- 基于核心
http
函数,为常见的 HTTP 方法(GET, POST, PUT, DELETE)提供了便捷的快捷方式。 - 简化了调用,只需传入 URL、数据(可选)和额外配置(可选)。
- 基于核心
-
独立性:
- 这个封装是自包含的,不依赖外部的状态管理库(如 Pinia/Vuex)或全局计数器。
-
结构化错误对象 (
HttpRequestError
):- 当请求失败时,Promise 会被拒绝(reject),并传递一个
HttpRequestError
对象。这个对象包含了错误信息 (message
)、原始的 uni-app 错误或响应对象 (originalError
)、可选的状态码 (statusCode
) 以及是否为网络错误的标志 (isNetworkError
),方便进行更细致的错误处理。
- 当请求失败时,Promise 会被拒绝(reject),并传递一个
总之,这个模块旨在提供一个更健壮、更易于使用的 HTTP 请求层,特别是在需要处理后端服务临时不可用(503)的情况下。