Result Type — 一种容器类型,用于表示可能以成功(Success)或错误(Failure)结束的操作结果。与异常不同,Result将错误作为普通值传递,无需展开堆栈,这使得对预期错误的处理更安全且更易组合。根据 Apple Swift Documentation (2026),Swift 中的 Result<Success, Failure> 允许通过 map 和 flatMap 将操作链与自动错误处理结合起来,而不会中断程序执行。
要点
Result Type 是一种泛型类型,用于封装可能以成功或错误结束的操作执行结果。与异常不同,异常会中断正常的执行流程并需要展开堆栈来查找 catch,而 Result 将错误作为普通值传递 —— 调用方始终接收一个对象并自行决定如何处理它。这对于预期错误尤其有用:无效输入、业务规则、服务器拒绝,在这些情况下使用异常机制会过于笨重。
Result 的概念源自函数式编程,其中类似的类型称为 Either(或 Left/Right)。在 Swift 中,Result 在 Swift 5.0 中成为标准类型;在 Kotlin 中,Result<T> 出现在标准库中;在 Dart 3.0 中 —— 内置的 Result<T>。每个实现都提供了用于处理容器的方法:map(转换成功值)、flatMap(Result 函数链)、mapError(转换错误)、fold(处理两种情况)。Result Type 是移动开发中函数式错误处理的基石,是 try-catch 在预期场景中的替代方案。
Result 的关键优势在于可组合性。您可以将多个可能返回错误的操作组合成一个链,无需嵌套的 try-catch。如果链中的任何操作以 Failure 结束,整个链将中断并返回 Failure —— 无需任何 if 条件或 catch 块。这使得代码线性且易读,特别是在多个连续 API 请求或业务规则检查的场景中。
在 Swift 中,Result<Success, Failure> 是一个包含两种情况的枚举:.success(Success) 和 .failure(Failure),其中 Failure 受 Error 协议约束。Swift Result 是一个完整的函数式类型,具有 map、flatMap、mapError 和 get 方法。get 是一个特殊方法:如果结果成功则返回 Success 值,如果是 Failure 则抛出错误。这使得 Result 可以作为函数式和基于异常的风格之间的桥梁:在链中通过 map/flatMap 处理,最后通过 do-catch 使用 get 与 throws 代码集成。
Swift Result 是一个带有泛型参数的枚举,允许编译器通过 switch 或 do-catch 检查穷尽处理。如果您向 NetworkError 枚举添加一个新的情况,编译器将在所有未处理此情况的 switch 表达式中报错。穷尽检查(exhaustive checking) 是 Result 相比异常的主要优势:编译器保证在编译阶段考虑所有可能的错误。相比之下,Swift 中的异常不会被编译器检查(只声明 throws,但不声明错误类型)。
enum NetworkError: Error {
case badURL
case requestFailed(String)
case decodingFailed
}
func fetchUser(id: Int) -> Result<User, NetworkError> {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
return .failure(.badURL)
}
let result = performRequest(url: url)
switch result {
case let .success(data):
if let user = try? JSONDecoder().decode(User.self, from: data) {
return .success(user)
}
return .failure(.decodingFailed)
case let .failure(error):
return .failure(.requestFailed(error.localizedDescription))
}
}
// 使用 switch 的用法
let result = fetchUser(id: 42)
switch result {
case .success(let user):
showUser(user)
case .failure(.badURL):
logError("Invalid URL")
case .failure(.requestFailed(let msg)):
showAlert(msg)
case .failure(.decodingFailed):
logError("Decoding error")
}
Result<Success, Failure> 允许在类型级别上对错误进行类型化:fetchUser 函数返回 Result<User, NetworkError>,其中 NetworkError 是一个包含三种情况的特定枚举。调用方通过具有穷尽覆盖的 switch 处理每种情况 —— 编译器检查所有变体是否都已处理。穷尽 switch 是 Result 相比异常的关键优势:编译器保证您不会忘记处理 .badURL、.requestFailed 或 .decodingFailed。在异常的情况下,编译器不要求处理,未捕获的 catch(.badURL) 在审查中很容易被忽略。
在 Kotlin 中,Result<T> 是标准库中的内置类型,表示成功(T)或错误(Throwable)。与 Swift 不同,在 Kotlin 中 Result 不允许指定具体的错误类型 —— 只能是 Throwable。这是为了简单起见,但需要通过 when 表达式进行额外的错误类型检查。Kotlin Result 支持 fold(处理两种情况)、getOrNull(成功或 null)、getOrDefault(成功或默认值)、map、recover、andThen 函数。Kotlin 的特点:Result 不用于通过函数边界直接传播 —— 如果没有额外的适配,它不能用作 Android SDK 或 Kotlin Coroutines API 方法的返回类型。
fun parseJson(input: String): Result<JsonObject> {
return runCatching {
JsonParser.parseString(input).asJsonObject()
}
}
fun validateEmail(email: String): Result<String> {
return if (email.contains("@")) {
Result.success(email.trim())
} else {
Result.failure(IllegalArgumentException("Invalid email"))
}
}
data class SignupData(val name: String, val email: String)
fun processSignup(name: String, email: String): SignupResult {
return validateEmail(email).fold(
onSuccess = { validateName(name) },
onFailure = { SignupResult.Error("Invalid email") }
)
}
runCatching —— Kotlin 中方便的包装器,可捕获任何异常并返回 Result.failure。validateEmail 根据检查返回 Result.success 或 Result.failure。fold 紧凑地处理两种情况:在 Success 时调用下一个验证函数,在 Failure 时返回 SignupResult.Error。重要提示:Kotlin Result 不用于存储在 data class 字段中或通过 suspend 函数边界直接传递 —— 请使用您自己的 sealed class(Success/Error/Loading)来表示 Jetpack Compose 或 MVVM 中的 UI 状态。
从 Dart 3.0 开始,标准库中出现了内置的 Result<T> —— 一种具有两个构造函数的 sealed class:T.ok()(成功)和 Error.error()(带有 Object 和 StackTrace 的错误)。在 Dart 3.0 之前,Flutter 开发人员使用 dartz 包中的 Either<L, R> 或自定义的 sealed class。Dart 中的内置 Result 是极简的:它不直接提供 map/flatMap —— 这些函数需要通过 when 或使用扩展方法来实现。对于严肃的函数式处理,来自 fpdart 的 Either 仍然是更强大的解决方案,支持 map、flatMap、mapLeft、fold、andThen 和 bind 操作符。
Either<L, R> —— 来自 fpdart 包的左关联类型,其中 Left 表示错误,Right 表示成功。与内置的 Result<T> 不同,Either 在类型参数(L)级别上对错误进行类型化,允许在编译阶段区分错误类型。fpdart 包提供了完整的函数式组合子集合:map(Right -> Right)、mapLeft(Left -> Left)、flatMap(bind —— 嵌套的 Either)、andThen(无转换的链)、fold(从 Either 中退出)、getOrElse(默认值)。对于采用函数式方法的 Flutter 应用程序,Either 是事实上的标准。
import 'dart:convert';
import 'package:fpdart/fpdart.dart';
class UserService {
Either<AppError, User> fetchUser(String id) {
try {
final response = await http.get(
Uri.parse('https://api.example.com/users/$id')
);
if (response.statusCode == 200) {
final user = User.fromJson(
json.decode(response.body)
);
return Either.of(user);
}
return Either.left(
AppError.serverError(response.statusCode)
);
} on SocketException catch (e) {
return Either.left(AppError.networkError(e.message));
}
}
}
// 使用 fold 的用法
final result = await service.fetchUser('42');
result.fold(
(left) => showError(left.message),
(right) => showUser(right),
);
Either<L, R> 来自 fpdart —— 左关联类型:Left —— 错误,Right —— 成功。fetchUser 返回 Either<AppError, User>,其中 AppError —— 带有特定错误类型的 sealed class(serverError、networkError)。fold 处理两种情况:第一个回调用于 Left(错误),第二个用于 Right(成功)。在 Dart 3.0 中,内置的 Result 也支持 fold,但不提供 map/flatMap。对于链,来自 fpdart 的 Either 提供 map、flatMap(bind)、mapLeft、andThen —— 用于错误组合的完整函数式组合子集合。
Result 相比异常的主要优势在于组合。如果您有多个可能返回错误的操作,您可以通过 map 和 flatMap 将它们组合成一个链,无需任何嵌套的 if 或 try-catch。map 转换成功的值:Result.success(x) -> Result.success(f(x))。flatMap(也称为 bind 或 andThen)—— 用于转换本身返回 Result 的情况:Result.success(x) -> f(x) -> Result<Y>。如果在任何步骤发生 Failure,后续操作将不会执行 —— 链被中断。
data class UserRequest(val userId: String, val token: String)
sealed class AuthError {
data object InvalidToken : AuthError()
data class UserNotFound(val id: String) : AuthError()
}
typealias Outcome<T> = Either<AuthError, T>
fun validateToken(token: String): Outcome<String> =
if (token.isNotBlank()) Either.right(token)
else Either.left(AuthError.InvalidToken)
fun fetchProfile(userId: String): Outcome<Profile> =
if (userId == "42") Either.right(Profile("Alice"))
else Either.left(AuthError.UserNotFound(userId))
// 通过 flatMap 进行组合(fpdart 中的 andThen)
val result = validateToken("abc123")
.flatMap { fetchProfile("42") }
.map { it.name }
.getOrElse { "Guest" }
println(result) // "Alice"
链:validateToken -> fetchProfile -> map name -> getOrElse “访客”。如果 validateToken 返回 Left(InvalidToken),链中断并返回 “访客”。如果 fetchProfile 返回 Left(UserNotFound)—— 也返回 “访客”。如果两个操作都成功 —— 返回个人资料名称。flatMap 允许将可能失败的 Either 函数组合成一个线性链。在传统的异常风格中,相同的代码需要两个嵌套的 try-catch 或 null 检查。getOrElse 在链的末尾 —— 组合的出口点,为 Failure 情况提供默认值。
Result 和异常 —— 并非互斥的方法。每个都有其应用领域,在精心设计的移动应用程序中,两者都会被使用。选择取决于错误是预期的(expected)还是意外的(unexpected)。Result —— 用于作为业务逻辑一部分的预期错误:无效的电子邮件、账户余额不足、超出请求限制。异常 —— 用于意外的系统错误:网络丢失、OutOfMemoryError、NullPointerException(不应该发生但确实会发生)。
| 标准 | Result Type | 异常(Exception/Error) |
|---|---|---|
| 错误类型 | 预期(业务逻辑) | 意外(系统) |
| 性能 | 成本低(无需展开堆栈) | 成本高(堆栈展开,捕获 StackTrace) |
| 组合 | 通过 map/flatMap —— 线性链 | 嵌套的 try-catch —— 难以阅读 |
| 编译器 | 穷尽检查(switch/when) | 仅在 Java 中有受检异常 |
| 执行流程 | 不中断 —— 错误作为值 | 中断到最近的 catch |
| 测试 | 容易:检查结果,assert isSuccess/isError | 需要 assertThrows 和 mock 对象 |
| 何时使用 | 业务验证、请求链、表单 | 网络丢失、I/O 错误、系统崩溃 |
实践经验:如果错误是应用程序正常工作流程的一部分(用户输入了无效的电子邮件、没有足够的访问权限)—— 使用 Result。如果错误是异常情况(服务器无响应、内存不足)—— 使用异常。在移动开发中,在层边界使用 Result(UseCase -> ViewModel)和在层内部使用异常(API -> Repository)是一种常见的模式,结合了两种方法的优点。
将现有的基于异常的代码迁移到 Result 应该逐步进行。从 层边界 开始:将 throws 函数的调用包装在 Result { try ... }(Swift)或 runCatching { ... }(Kotlin)中。然后,将 Repository 和 UseCase 方法的返回类型替换为 Result/Either,保留内部实现在异常上。在最后阶段,迁移 ViewModel:使用 sealed class UiState<T>(Loading、Success、Error)代替带有异常的 UiState,其中 Error 存储领域错误,而不是 Throwable。逐步迁移允许单独测试每一层,无需全局重构。
常见问题解答
Optional(T?) 表示值的存在或不存在 —— nil 意味着 "没有数据",但不说明原因。Result(Success/Failure) 不仅包含成功,还包含带有具体类型的错误原因。当值不存在是正常情况时(例如,可选的个人资料字段),使用 Optional;当需要错误信息时,使用 Result。
在 Swift 中使用 Result { try throwingFunc() } —— Result 的构造函数接受 throws 闭包。在 Kotlin 中使用 runCatching { throwingFunc() },它返回 Result<T>。在 Dart 中使用 Result<T>.tryCatch(() => throwingFunc())。这允许将基于异常的代码轻松集成到 Result 链中。
使用 mapError(Swift)或 mapLeft(Dart/Kotlin 中的 Either)来转换错误类型,而不更改成功的值。如果需要处理两种情况并返回单一值 —— 使用 fold。要记录日志而不中断链,请使用 onFailure(Kotlin)或前缀检查点。
可以,但需要谨慎。由于 K2 编译器和反射的特性,Kotlin Result<T> 不建议直接用作 suspend 函数的返回类型。使用您自己的 sealed class NetworkResult<T>(Success、Error、Loading)来表示协程中的状态。对于业务逻辑中的预期错误,来自 Arrow 的 Either 是更强大的替代方案。
fold —— 一种接受两个回调的方法:onSuccess(用于成功情况)和 onFailure(用于错误情况),并返回任何类型的单一值。它相当于 switch 表达式,但以高阶函数的形式存在。fold —— 从 Result 链中退出的主要出口点,您在此处将 Success/Failure 转换为 UiState、用户字符串或其他 Result。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。