用Kotlin Result编程: kotlin.Result

1,208 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情

用Kotlin Result编程: kotlin.Result

对处理Result的最重要的标准库函数的简短浏览 - 使用fold()进行一般转换, 使用getOrThrow(), getOrElse()/getOrDefault()检索值, 使用map()/mapCatching()映射成功, 使用recover()/recoverCatching()映射失败和使用onSuccess()/onFailure()窥探.

kotlin.Result类型被添加到标准库中, 以解决我们在以前文章中谈到的问题, 除此之外. 它的实现与我们在上一节介绍的有点不同 - 它不是一个密封的类层次结构, 而是作为一个单一的值类来实现.

除此之外, 它与我们自己设计的基本相同 - 它在语义上仍然等同于Result = Success(T) | Failure, 尽管它没有以这种方式实现.

要手动创建一个成功或失败的值, 请使用定义在同伴对象上的Result.success(value: T)Result.failing(exception: Throwable)方法. 你可以使用isSuccessisFailure方法确定Result是成功还是失败, 并使用getOrNull()exceptionOrNull()访问该值/Throwable. 然而, 你很少需要这些, 因为它们提倡更多的命令式编程风格, 而应该选择下面描述的标准函数之一.

标准库还定义了一个runCatching方法, 与我们之前使用的方法完全相同.

还有一个接收器版本, T.runCatching. 基本上, T.runCatchingT.run来说就像runCatchingrun一样:

标准函数

所有标准化的函数都是以下函数的专门版本:

inline fun <T, R> Result<T>.fold(
    onSuccess: (value: T) -> R,
    onFailure: (exception: Throwable) -> R
): R

这将改变Result中包含的值, 取决于它是成功还是失败. 实质上, 如果Result是成功的, 该函数返回 onSuccess(this.getOrNull()!!), 否则返回onFailure(this.exceptionOrNull()!).

检索值

getOrThrow()

fun <T> Result<T>.getOrThrow(): T

返回成功值, 或者抛出. 虽然这看起来是一个微不足道的函数, 但在组成返回Result的方法时, 它实际上是非常有用的, 而且这个主题非常有趣, 我们将用一个单独的章节来介绍它.

等价于: fold({ it }, { throw it }).

getOrElse(), getOrDefault()

inline fun <T, R> Result<T>.getOrElse(
    onFailure: (exception: Throwable) -> R
): R fun <T, R> Result<T>.getOrDefault(defaultValue: R): R

返回成功值, 或将异常映射为一个/返回一个默认值:

等价于: fold({ it }, ::onFailure)/fold({ it }, { defaultValue })

映射成功的Result

map()

inline fun <T, R> Result<T>.map(
    transform: (value: T) -> R
): Result<R>

转化成功的值. 在transform内部抛出的异常将会重抛.

等价于: fold({ Result.success(transform(it)) }, { Result.failure(it) })

mapCatching()

inline fun <T, R> Result<T>.mapCatching(
    transform: (value: T) -> R
): Result<R>

转化成功的值, 在transform内部抛出的异常将会转化成失败.

等价于: fold({ runCatching { transform(it) } }, { Result.failure(it) })

你应该使用哪种方法?

只在转换一个单个结果时使用map/mapCatching. 我们将在下一篇文章中讨论合并和合成多个结果.

如果转换的业务/技术本质不能导致失败(例如, 将一个值乘以2), 则使用map. 如果转换的本质可以导致失败(即保存记录, 执行业务计算等), 则使用mapCatching.

我强调转换的本质, 因为从理论上讲, 每一行都可以抛出OutOfMemory, ThreadDeath等错误. 用 runCatching来包装每一行真的没有意义 - 这些错误, 如果它们真的发生在做乘以2这样的小事时, 会被该方法的上游调用者抓住.

转换失败的结果

recover()

inline fun <T, R> Result<T>.recover(
    transform: (exception: Throwable) -> R
): Result<R>

转化一个失败值(R需要可分配给T). 与map类似, 但对失败进行操作. 和map一样, 在transform中抛出的异常会被重新抛出.

等价于: fold({ Result.success(it) }, { Result.success(transform(it)) })

recoverCatching()

inline fun <T, R> Result<T>.recoverCatching(
    transform: (exception: Throwable) -> R
): Result<R>

转化一个失败值, 将transform中抛出的任何异常转换为失败. 与mapCatching类似, 但对失败进行操作.

你应该使用哪种方法?

map一样, 如果转换是原子性的和/或转换的业务/技术本质不包括失败, 则使用recover. 如果转换是复杂的, 和/或转换的本质包括失败, 则使用recoverCatching.

窥探

onSuccess(), onFailure()

inline fun <T> Result<T>.onSuccess(
    action: (value: T) -> Unit
): Result<T>inline fun <T> Result<T>.onFailure(
    action: (exception: Throwable) -> Unit
): Result<T>

如果Result是一个成功/失败的结果, 就执行动作. 返回原始的Result, 不做任何改变. 这些函数基本上等同于also, 但在Result的上下文中. 它们的使用情况是一样的.

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情