使用 Result 对象进行错误处理
使用 Result 对象改善跨类的错误处理。
Dart 提供内置错误处理机制,支持抛出与捕获异常。
如 错误处理文档 所述,Dart 的异常是未处理异常 (unhandled exceptions)。这意味着抛出异常的方法无需声明异常,调用方也不必捕获。
这可能导致异常未得到妥善处理。在大型项目中,开发者可能忘记捕获异常,各层与组件可能抛出未文档化的异常,进而导致错误与崩溃。
本指南将介绍这一局限,以及如何用 结果类型 (result) 模式缓解。
Flutter 应用中的错误流
#遵循 Flutter 架构指南 的应用通常由 view model、repository、service 等组成。当其中某组件的函数失败时,应将错误告知调用方。
通常通过异常完成。例如,无法与远程服务器通信的 API 客户端 service 可能抛出 HTTP 错误异常;调用方(如 Repository)须捕获该异常,或忽略并由 view model 处理。
以下示例可见这一点。考虑这些类:
-
Service
ApiClientService向远程服务发起 API 调用。 -
Repository
UserProfileRepository提供由ApiClientService获取的UserProfile。 -
View model
UserProfileViewModel使用UserProfileRepository。
ApiClientService 的 getUserProfile 在特定情况下会抛出异常:
-
响应码非 200 时抛出
HttpException。 -
响应格式不正确时 JSON 解析抛出异常。
HTTP 客户端可能因网络问题抛出异常。
以下代码处理多种可能的异常:
class ApiClientService {
// ···
Future<UserProfile> getUserProfile() async {
try {
final request = await client.get(_host, _port, '/user');
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
return UserProfile.fromJson(jsonDecode(stringData));
} else {
throw const HttpException('Invalid response');
}
} finally {
client.close();
}
}
}
UserProfileRepository 无需处理 ApiClientService 的异常;本例中它直接返回 API 客户端的值。
class UserProfileRepository {
// ···
Future<UserProfile> getUserProfile() async {
return await _apiClientService.getUserProfile();
}
}
最后,UserProfileViewModel 应捕获所有异常并处理错误。
可用 try-catch 包装对 UserProfileRepository 的调用:
class UserProfileViewModel extends ChangeNotifier {
// ···
Future<void> load() async {
try {
_userProfile = await userProfileRepository.getUserProfile();
notifyListeners();
} on Exception catch (exception) {
// handle exception
}
}
}
现实中开发者可能忘记正确捕获异常,写出如下代码。它能编译运行,但若前述任一异常发生则会崩溃:
class UserProfileViewModel extends ChangeNotifier {
// ···
Future<void> load() async {
_userProfile = await userProfileRepository.getUserProfile();
notifyListeners();
}
}
可尝试为 ApiClientService 文档化可能抛出的异常。但 view model 不直接使用 service,其他开发者可能忽略该信息。
使用结果类型模式
#抛异常的替代方案是将函数输出包装在 Result 对象中。
成功时 Result 含返回值;失败时含错误。
Result 是 sealed
类,子类为 Ok 或 Error;成功值用 Ok 返回,捕获的错误用 Error 返回。
以下是为演示简化的 Result 示例,完整实现见文末。
/// Utility class that simplifies handling errors.
///
/// Return a [Result] from a function to indicate success or failure.
///
/// A [Result] is either an [Ok] with a value of type [T]
/// or an [Error] with an [Exception].
///
/// Use [Result.ok] to create a successful result with a value of type [T].
/// Use [Result.error] to create an error result with an [Exception].
sealed class Result<T> {
const Result();
/// Creates an instance of Result containing a value
factory Result.ok(T value) => Ok(value);
/// Create an instance of Result containing an error
factory Result.error(Exception error) => Error(error);
}
/// Subclass of Result for values
final class Ok<T> extends Result<T> {
const Ok(this.value);
/// Returned value in result
final T value;
}
/// Subclass of Result for errors
final class Error<T> extends Result<T> {
const Error(this.error);
/// Returned error in result
final Exception error;
}
本例中 Result 用泛型 T 表示任意返回值,可为 String、int 或 UserProfile
等。
创建 Result 对象
#
使用 Result 返回值的函数不再直接返回值,而是返回包含值的 Result。
例如 ApiClientService 的 getUserProfile 改为返回 Result:
class ApiClientService {
// ···
Future<Result<UserProfile>> getUserProfile() async {
// ···
}
}
不再直接返回 UserProfile,而是返回包含 UserProfile 的 Result。
Result 提供 Result.ok 与 Result.error 命名构造函数,按输出构造 Result,并捕获代码抛出的异常包装进 Result。
例如 getUserProfile() 已改为使用 Result:
class ApiClientService {
// ···
Future<Result<UserProfile>> getUserProfile() async {
try {
final request = await client.get(_host, _port, '/user');
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
return Result.ok(UserProfile.fromJson(jsonDecode(stringData)));
} else {
return const Result.error(HttpException('Invalid response'));
}
} on Exception catch (exception) {
return Result.error(exception);
} finally {
client.close();
}
}
}
原 return 改为 Result.ok 返回;throw HttpException() 改为 Result.error(HttpException());并用 try-catch 将 HTTP 客户端或 JSON 解析器抛出的异常捕获为 Result.error。
Repository 类也需修改,直接返回 UserProfile 改为返回 Result<UserProfile>。
Future<Result<UserProfile>> getUserProfile() async {
return await _apiClientService.getUserProfile();
}
解包 Result 对象
#现在 view model 收到的是包含 UserProfile 的 Result,而非直接的 UserProfile。
这迫使实现 view model 的开发者解包 Result 获取 UserProfile,避免未捕获异常。
class UserProfileViewModel extends ChangeNotifier {
// ···
UserProfile? userProfile;
Exception? error;
Future<void> load() async {
final result = await userProfileRepository.getUserProfile();
switch (result) {
case Ok<UserProfile>():
userProfile = result.value;
case Error<UserProfile>():
error = result.error;
}
notifyListeners();
}
}
Result 用 sealed 实现,只能是 Ok 或 Error,可用 switch 结果或表达式
求值。
Ok<UserProfile> 时用 value 属性获取值。
Error<UserProfile> 时用 error 属性获取错误对象。
改善控制流
#try-catch 确保抛出的异常被捕获而不传播到其他代码。
考虑以下代码。
class UserProfileRepository {
// ···
Future<UserProfile> getUserProfile() async {
try {
return await _apiClientService.getUserProfile();
} catch (e) {
try {
return await _databaseService.createTemporaryUser();
} catch (e) {
throw Exception('Failed to get user profile');
}
}
}
}
此方法中 UserProfileRepository 先通过 ApiClientService 获取 UserProfile,失败则尝试在 DatabaseService 创建临时用户。
因两种 service 方法都可能失败,代码须在两种情况下捕获异常。
可用 Result 模式改进:
Future<Result<UserProfile>> getUserProfile() async {
final apiResult = await _apiClientService.getUserProfile();
if (apiResult is Ok) {
return apiResult;
}
final databaseResult = await _databaseService.createTemporaryUser();
if (databaseResult is Ok) {
return databaseResult;
}
return Result.error(Exception('Failed to get user profile'));
}
若 Result 为 Ok 则返回该对象,否则返回 Result.error。
总结
#本指南介绍了如何使用 Result 类返回结果值。
要点:
-
Result类迫使调用方检查错误,减少未捕获异常导致的 bug。 -
相比 try-catch,
Result类有助于改善控制流。 -
Result类为sealed,只能为Ok或Error,可用 switch 解包。
下文为 Flutter 架构指南 的 Compass 应用示例
中的完整 Result 类。
/// Utility class that simplifies handling errors.
///
/// Return a [Result] from a function to indicate success or failure.
///
/// A [Result] is either an [Ok] with a value of type [T]
/// or an [Error] with an [Exception].
///
/// Use [Result.ok] to create a successful result with a value of type [T].
/// Use [Result.error] to create an error result with an [Exception].
///
/// Evaluate the result using a switch statement:
/// ```dart
/// switch (result) {
/// case Ok(): {
/// print(result.value);
/// }
/// case Error(): {
/// print(result.error);
/// }
/// }
/// ```
sealed class Result<T> {
const Result();
/// Creates a successful [Result], completed with the specified [value].
const factory Result.ok(T value) = Ok._;
/// Creates an error [Result], completed with the specified [error].
const factory Result.error(Exception error) = Error._;
}
/// A successful [Result] with a returned [value].
final class Ok<T> extends Result<T> {
const Ok._(this.value);
/// The returned value of this result.
final T value;
@override
String toString() => 'Result<$T>.ok($value)';
}
/// An error [Result] with a resulting [error].
final class Error<T> extends Result<T> {
const Error._(this.error);
/// The resulting error of this result.
final Exception error;
@override
String toString() => 'Result<$T>.error($error)';
}
除非另有说明,本文档之所提及适用于 Flutter 3.44.0 版本。本页面最后更新时间:2026-06-22。查看文档源码 或者 为本页面内容提出建议。