send<T> method
Future<Response<T> >
send<T>({
- required String method,
- required String path,
- dynamic data,
- Map<
String, dynamic> ? queryParameters, - bool isLoading = false,
- Map<
String, String> ? headers, - int priority = 0,
- bool skipDeduplication = false,
- bool skipQueue = false,
- String? baseUrl,
- String? service,
- bool isChainCall = false,
- void onFailure()?,
发送请求(自动处理异常,失败时自动提示)
method 请求方式:必须使用 hm.get、hm.post 等常量
isLoading 是否显示加载提示(默认 false)
如果为 true 且配置了 contextGetter,将自动显示加载提示
headers 特定请求的请求头(可选),会与全局请求头合并,如果键相同则覆盖全局请求头
链式调用中的加载提示:
推荐使用 http.isLoading.send() 来明确标记链式调用,整个链路共享一个加载提示
加载提示会在整个链路结束时(成功或失败)自动关闭
请求头优先级:
- 特定请求的 headers(最高优先级,会覆盖全局请求头)
- 动态请求头(dynamicHeaderBuilder)
- 静态请求头(staticHeaders)
示例(链式调用,推荐方式):
final result = await http.isLoading
.send(
method: hm.post,
path: '/uploader/generate',
data: {'ext': 'jpg'},
)
.extractModel<FileUploadResult>(FileUploadResult.fromConfigJson)
.thenWith((uploadResult) => http.uploadToUrlResponse(...));
示例(单次请求):
final response = await http.send(
method: hm.post,
path: '/auth/login',
isLoading: true, // 单次请求,请求完成后自动关闭 loading
);
示例(特定请求头):
// 某个接口需要特定的请求头
final response = await http.send(
method: hm.post,
path: '/special-endpoint',
data: {'key': 'value'},
headers: {'X-Custom-Header': 'custom-value'}, // 特定请求头,会覆盖全局同名请求头
);
示例(多服务支持):
// 配置
HttpUtil.configure(
HttpConfig(
baseUrl: 'https://api.example.com/v1',
serviceBaseUrls: {
'files': 'https://files.example.com',
'cdn': 'https://cdn.example.com',
},
),
);
// 使用默认 baseUrl
await http.send(method: hm.get, path: '/users');
// 使用服务
await http.send(method: hm.post, path: '/upload', service: 'files');
// 直接指定 baseUrl(最高优先级)
await http.send(
method: hm.get,
path: '/data',
baseUrl: 'https://custom.example.com',
);
Implementation
Future<Response<T>> send<T>({
required String method,
required String path,
dynamic data,
Map<String, dynamic>? queryParameters,
bool isLoading = false,
Map<String, String>? headers,
int priority = 0,
bool skipDeduplication = false,
bool skipQueue = false,
String? baseUrl, // 直接指定 baseUrl(最高优先级)
String? service, // 使用 serviceBaseUrls 中定义的服务名称
bool isChainCall = false, // 内部参数:标记是否为链式调用(由 isLoading getter 使用)
void Function(int? httpStatusCode, int? errorCode, String message)?
onFailure, // 请求级别的错误处理回调
}) async {
// 实际执行请求的函数
Future<Response<T>> executeRequest() async {
String? loadingId;
bool isChainCallFlag = isChainCall; // 使用参数传入的 isChainCall
// 如果需要显示加载提示
if (isLoading) {
final config = _config;
if (config?.contextGetter != null) {
final context = config!.contextGetter!();
if (context != null) {
// 检查是否已有链式调用的加载提示
if (HttpUtil._chainLoadingId != null) {
// 链式调用中已有加载提示,复用现有的
loadingId = HttpUtil._chainLoadingId;
isChainCallFlag = true;
} else {
// 创建新的加载提示
loadingId = _showLoading(context, config);
if (loadingId != null) {
HttpUtil._chainLoadingId = loadingId;
// 如果通过 isLoading getter 调用,isChainCall 参数为 true
// 如果是单次请求,isChainCall 参数为 false
isChainCallFlag = isChainCall; // 使用外部传入的 isChainCall 参数
}
}
}
}
}
try {
// 解析 baseUrl
final resolvedBaseUrl =
HttpUtilSafeCall._resolveBaseUrl(baseUrl, service);
final config = _config;
if (config == null) {
throw StateError('HttpUtil 未配置,请先调用 HttpUtil.configure() 进行配置');
}
// 构建请求选项(responseType.plain 拿原始字符串,再在 isolate 中 decode 避免 UI 卡顿)
final options = dio_package.Options(
headers: headers ?? const {},
responseType: dio_package.ResponseType.plain,
);
// 直接调用 request 方法获取原始 response
final rawResponse = await HttpUtil.instance.request<T>(
method: method,
path: path,
data: data,
queryParameters: queryParameters,
options: options,
baseUrl: resolvedBaseUrl,
);
// 检查 500 错误
if (rawResponse.statusCode == 500) {
return _handleNetworkError<T>();
}
// 在 isolate 中解码 JSON,避免主线程解析大 JSON 导致 UI 卡顿
dynamic responseData = rawResponse.data;
if (responseData is String) {
try {
responseData = await compute<String, dynamic>(_decodeJsonInIsolate, responseData);
} catch (_) {
// 解码失败保持原样,由解析器返回格式错误
}
}
final raw = RawHttpResponse(
statusCode: rawResponse.statusCode,
data: responseData,
path: rawResponse.requestOptions.path,
);
final response = config.responseParser.parse<T>(raw);
// 自动处理错误(如果用户实现了 handleError 方法)
if (!response.isSuccess) {
response.handleError();
// 延迟调用全局的错误处理回调,确保链式调用的 onFailure 可以先执行
// 优先级:链式调用的 onFailure > on401Unauthorized > 全局的 onFailure
final errorMessage = response.errorMessage;
if (errorMessage != null) {
final httpStatusCode = response.httpStatusCode; // 获取 HTTP 状态码
final errorCode = response.errorCode; // 获取业务错误码
Future.microtask(() {
// 再次检查 errorHandled,如果链式调用的 onFailure 已经处理了,就不调用其他错误处理
if (!response.errorHandled) {
// 优先级 1:send 的 onFailure(请求级别的错误处理)
if (onFailure != null) {
onFailure(httpStatusCode, errorCode, errorMessage);
// 通过调用 response.onFailure 来标记错误已处理(传入空回调,只用于标记)
// 这样可以复用现有的标记逻辑,避免直接访问私有方法
response.onFailure((_, __, ___) {});
return;
}
// 优先级 2:401 且设置了 on401Unauthorized
if (httpStatusCode == 401 && config.on401Unauthorized != null) {
// 检查是否需要去重
if (HttpUtilSafeCall._shouldHandleError(
401, config.errorDeduplicationWindow)) {
config.on401Unauthorized!();
}
// 401 已由 on401Unauthorized 处理,不再调用 onFailure
return;
}
// 优先级 3:全局的 onFailure
if (config.onFailure != null) {
config.onFailure!(httpStatusCode, errorCode, errorMessage);
}
}
});
}
}
return response;
} catch (e) {
// 所有异常都统一处理为网络错误
return _handleNetworkError<T>();
} finally {
// 如果不是链式调用(通过 http.isLoading.send() 标记),延迟关闭加载提示并清理 _chainLoadingId
// 使用 Future.microtask 确保在扩展方法(如 extractField、onSuccess、extractModel)之后执行
// 如果是链式调用(isChainCallFlag = true),加载提示会在整个链路结束时关闭(由 thenWithUpdate 等方法关闭)
// 注意:extractModel、extractField 等方法不再关闭 loading,统一由 finally 块或链式调用的最后一步关闭
if (isLoading && loadingId != null && !isChainCallFlag) {
Future.microtask(() {
// 再次检查 loadingId 是否仍然存在,并且确保不是链式调用
// 如果用户只使用了 await http.send(isLoading: true),这里会关闭 loading
// 如果用户使用了 extractModel 等方法但没有后续链式调用,这里也会关闭 loading
// 如果用户使用了 thenWith 等链式调用,loading 会由链式调用的最后一步关闭
// 关键:如果 _chainLoadingId 仍然存在且等于当前 loadingId,说明没有后续链式调用,可以关闭
// 如果 _chainLoadingId 已经被后续链式调用修改或清空,说明有链式调用,不应该关闭
if (HttpUtil._chainLoadingId == loadingId) {
_hideLoading(loadingId);
HttpUtil._chainLoadingId = null;
}
});
}
}
}
// 如果启用了队列且未跳过队列,加入队列
if (HttpUtil._requestQueue != null && !skipQueue) {
return HttpUtil._requestQueue!.enqueue<Response<T>>(
priority: priority,
requestExecutor: () {
// 如果启用了去重且未跳过去重,使用去重管理器
if (HttpUtil._deduplicator != null && !skipDeduplication) {
// 解析 baseUrl 用于去重
final resolvedBaseUrl =
HttpUtilSafeCall._resolveBaseUrl(baseUrl, service);
return HttpUtil._deduplicator!.execute<Response<T>>(
method: method,
path: path,
queryParameters: queryParameters,
data: data,
baseUrl: resolvedBaseUrl,
requestExecutor: executeRequest,
);
} else {
return executeRequest();
}
},
);
}
// 如果启用了去重且未跳过去重,使用去重管理器
if (HttpUtil._deduplicator != null && !skipDeduplication) {
// 解析 baseUrl 用于去重
final resolvedBaseUrl =
HttpUtilSafeCall._resolveBaseUrl(baseUrl, service);
return HttpUtil._deduplicator!.execute<Response<T>>(
method: method,
path: path,
queryParameters: queryParameters,
data: data,
baseUrl: resolvedBaseUrl,
requestExecutor: executeRequest,
);
}
// 直接执行请求
return executeRequest();
}