Result Type:什么是Result结果容器类型以及它在移动开发中的工作原理

作者: IT Sectr 发布日期: 2026-05-26 阅读时间: 9 分钟

Result Type — 一种容器类型,用于表示可能以成功(Success)或错误(Failure)结束的操作结果。与异常不同,Result将错误作为普通值传递,无需展开堆栈,这使得对预期错误的处理更安全且更易组合。根据 Apple Swift Documentation (2026),Swift 中的 Result<Success, Failure> 允许通过 map 和 flatMap 将操作链与自动错误处理结合起来,而不会中断程序执行。

要点

  • Result Type — 具有两种状态的泛型容器:Success(数据)和 Failure(错误),无需展开堆栈
  • 在 Swift 中 Result<Success, Failure> — 内置类型,具有 map、flatMap、mapError 和 get 方法
  • 在 Kotlin 中 Result<T> 表示成功或 Throwable,具有 getOrNull、getOrDefault、fold 函数
  • 在 Dart 中 标准 Result<T> 从 Dart 3.0 开始可用,还有来自 fpdart 包的 Either
  • 组合 通过 map 和 flatMap 可以将多个返回 Result 的操作组合起来,无需嵌套检查

什么是 Result Type?

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

在 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 代码集成。

Result 枚举和穷尽 switch

Swift Result 是一个带有泛型参数的枚举,允许编译器通过 switch 或 do-catch 检查穷尽处理。如果您向 NetworkError 枚举添加一个新的情况,编译器将在所有未处理此情况的 switch 表达式中报错。穷尽检查(exhaustive checking) 是 Result 相比异常的主要优势:编译器保证在编译阶段考虑所有可能的错误。相比之下,Swift 中的异常不会被编译器检查(只声明 throws,但不声明错误类型)。

swift
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

在 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 方法的返回类型。

kotlin
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 和 Flutter 中的 Result

从 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 操作符。

来自 fpdart 的 Either

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 是事实上的标准。

dart
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 —— 用于错误组合的完整函数式组合子集合。

通过 map 和 flatMap 组合 Result

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,后续操作将不会执行 —— 链被中断。

kotlin
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 与异常:何时选择什么

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 的策略

将现有的基于异常的代码迁移到 Result 应该逐步进行。从 层边界 开始:将 throws 函数的调用包装在 Result { try ... }(Swift)或 runCatching { ... }(Kotlin)中。然后,将 Repository 和 UseCase 方法的返回类型替换为 Result/Either,保留内部实现在异常上。在最后阶段,迁移 ViewModel:使用 sealed class UiState<T>(Loading、Success、Error)代替带有异常的 UiState,其中 Error 存储领域错误,而不是 Throwable。逐步迁移允许单独测试每一层,无需全局重构。

常见问题解答

Result Type 与 Optional/Option 有什么区别?

Optional(T?) 表示值的存在或不存在 —— nil 意味着 "没有数据",但不说明原因。Result(Success/Failure) 不仅包含成功,还包含带有具体类型的错误原因。当值不存在是正常情况时(例如,可选的个人资料字段),使用 Optional;当需要错误信息时,使用 Result。

如何将 throws 函数转换为 Result?

在 Swift 中使用 Result { try throwingFunc() } —— Result 的构造函数接受 throws 闭包。在 Kotlin 中使用 runCatching { throwingFunc() },它返回 Result<T>。在 Dart 中使用 Result<T>.tryCatch(() => throwingFunc())。这允许将基于异常的代码轻松集成到 Result 链中。

如何在 Result 中处理错误而不丢失信息?

使用 mapError(Swift)或 mapLeft(Dart/Kotlin 中的 Either)来转换错误类型,而不更改成功的值。如果需要处理两种情况并返回单一值 —— 使用 fold。要记录日志而不中断链,请使用 onFailure(Kotlin)或前缀检查点。

可以在 Kotlin 中将 Result 与协程一起使用吗?

可以,但需要谨慎。由于 K2 编译器和反射的特性,Kotlin Result<T> 不建议直接用作 suspend 函数的返回类型。使用您自己的 sealed class NetworkResult<T>(Success、Error、Loading)来表示协程中的状态。对于业务逻辑中的预期错误,来自 Arrow 的 Either 是更强大的替代方案。

在 Result 的上下文中,fold 是什么?

fold —— 一种接受两个回调的方法:onSuccess(用于成功情况)和 onFailure(用于错误情况),并返回任何类型的单一值。它相当于 switch 表达式,但以高阶函数的形式存在。fold —— 从 Result 链中退出的主要出口点,您在此处将 Success/Failure 转换为 UiState、用户字符串或其他 Result。

总结

  • Result Type —— 用于安全处理预期错误的泛型容器(Success/Failure),无需展开堆栈
  • 在 Swift 中 Result<Success, Failure> 与 Failure: Error 通过 switch 提供穷尽检查,具有类型安全的错误
  • 在 Kotlin 中 Result<T> 包装成功或 Throwable,runCatching —— 来自 throws 代码的便捷构造函数
  • 在 Dart 中 内置的 Result<T>(Dart 3.0)和来自 fpdart 的 Either<L, R> 用于高级组合,具有 map/flatMap
  • 组合 通过 map(成功转换)和 flatMap(Result 函数链)替代嵌套的 try-catch
  • Result 与异常: Result 用于预期的业务错误,异常用于意外的系统故障
  • 层边界使用 Result 以实现显式且可测试的错误处理,而不中断执行流程

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读