ITmob-Ly
发布于 2022-12-26 / 1007 阅读
0

Kotlin 中 Result 类型的详解和用法

1. 介绍

当一些方法或操作需要得到结果并且需要知道操作是否成功进行统一的操作和管理(如:网络请求,加载数据库中的数据),成功时需要得到结果,失败时需要对失败的异常进行处理,这时我们需要定义一个类包装操作的结果,其中既需要定义成功时的数据也需要定义失败时的异常。

使用 Kotlin 时其实我们并不用急于去自定义一个类去封装返回结果,Kotlin API 提供了一个 Result 类型来帮我们处理这种操作,它封装了返回值,既可以是支持泛型的结果,也可以是表示失败的 Throwable 类型,来表示成功和失败两种状态。

并且 Result 类型中定义了一个伴生对象可以方便的在操作时返回成功和失败的结果。还有多个扩展方法方便我们的操作。

2. 构造函数和伴生对象

如下是 Result 类型的部分源码:

/**
 * A discriminated union that encapsulates a successful outcome with a value of type [T]
 * or a failure with an arbitrary [Throwable] exception.
 */
@SinceKotlin("1.3")
@JvmInline
public value class Result<out T> @PublishedApi internal constructor(
    @PublishedApi
    internal val value: Any?
) : Serializable {
    ...

    /**
     * Returns `true` if this instance represents a successful outcome.
     * In this case [isFailure] returns `false`.
     */
    public val isSuccess: Boolean get() = value !is Failure

    /**
     * Returns `true` if this instance represents a failed outcome.
     * In this case [isSuccess] returns `false`.
     */
    public val isFailure: Boolean get() = value is Failure

    /**
     * Companion object for [Result] class that contains its constructor functions
     * [success] and [failure].
     */
    public companion object {
        /**
         * Returns an instance that encapsulates the given [value] as successful value.
         */
        @Suppress("INAPPLICABLE_JVM_NAME")
        @InlineOnly
        @JvmName("success")
        public inline fun <T> success(value: T): Result<T> =
            Result(value)

        /**
         * Returns an instance that encapsulates the given [Throwable] [exception] as failure.
         */
        @Suppress("INAPPLICABLE_JVM_NAME")
        @InlineOnly
        @JvmName("failure")
        public inline fun <T> failure(exception: Throwable): Result<T> =
            Result(createFailure(exception))
    }
    ...
}

如源码所示 Result 类型的构造函数包含一个 Any? 类型的属性 value,如果需要 Result 表示操作成功时就可以使用伴生对象中的 success 方法封装操作的结果,这时 Result 的 value 就是操作成功要返回的数据,失败时通过 failure 方法 value 就是失败时的异常对象。

Result 类型被定义成了内联类,因为 Result 类更关心的是其中所持有的数据而不是 Result 本身。不了解为什么使用内联类的,可以查看内联类的相关文档。

internal constructor 表明 Result 的构造函数仅是同 module 可见的,我们不能直接创建 Result 对象。只可以通过它的伴生对象中的 success 和 failure 方法创建,这也保证了 Result 更能清晰的表示和封装成功和失败两个状态。

3. 成员属性和方法

成员属性:

  • isSuccess
// value不是 Failure 类型即为成功
public val isSuccess: Boolean get() = value !is Failure
  • isFailure
// value是 Failure 类型即为失败
public val isFailure: Boolean get() = value is Failure

方法:

  • getOrNull
// 如果成功则返回 value,失败则返回 null,不会返回失败的 exception
public inline fun getOrNull(): T? =
    when {
        isFailure -> nullelse -> value as T
    }
  • exceptionOrNull
// 如果成功则返回 null,失败则返回 Throwable 对象,不会返回成功的数据
public fun exceptionOrNull(): Throwable? =
    when (value) {
        is Failure -> value.exception
        else -> null
    }
  • toString
// toString 方法
public override fun toString(): String =
    when(value) {
        isFailure ->value.toString()// "Failure($exception)"
        else->"Success($value)"
    }

以上就是 Result 类型的所以成员函数和方法,可以完成 Result 的基本操作。下面介绍源码中提供的扩展函数。

4. 扩展函数

  • runCatching

调用 runCatching 将要执行的方法作为参数调用,可以帮助我们省去显式的 try catch 可能抛出的异常,抛出异常时自动返回包含异常对象的 Result

// 执行 block 函数并将返回值返回值封装为 Result 类型
public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

// 将 runCatching 作为扩展函数调用,执行 T 类型的 block 函数,返回封装 R 类型数据的 Result 对象
public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}
  • getOrThrow
// 跟 getOrNull 类似,如果失败则将失败的 Throwable 异常对象抛出
public inline fun <T> Result<T>.getOrThrow(): T {
    throwOnFailure()
    return value as T
}
  • getOrElse
// 跟 getOrThrow 类似,如果失败不会直接抛出异常,而是执行传入的 onFailure 并返回运行结果
public inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R {
    contract {
        // 保证 onFailure 只执行一次
        callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> value as T
        else -> onFailure(exception)
    }
}
  • getOrDefault
// 跟 getOrElse 类似,调用时传人一个默认值,默认值必须是 T 类型的子类
public inline fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R {
    if (isFailure) return defaultValue
    return value as T
}
  • fold
// 接受 onSuccess 和 onFailure 两个方法,并分别在成功和失败的时候调用,返回 R 类型对象
public inline fun <R, T> Result<T>.fold(
    onSuccess: (value: T) -> R,
    onFailure: (exception: Throwable) -> R
): R {
    contract {
        callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
        callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> onSuccess(value as T)
        else -> onFailure(exception)
    }
}
  • map
// 接受 transform 方法,将 T 类型的数据映射成 R 类型
public inline fun <R, T> Result<T>.map(transform: (value: T) -> R): Result<R> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    return when {
        isSuccess -> Result.success(transform(value as T))
        else -> Result(value)
    }
}
  • mapCatching
// map 和 runCatching 方法的结合
public inline fun <R, T> Result<T>.mapCatching(transform: (value: T) -> R): Result<R> {
    return when {
        isSuccess -> runCatching { transform(value as T) }
        else -> Result(value)
    }
}
  • recover
// 在失败的时候,执行接受的 transform 将异常“恢复”为成功的结果
public inline fun <R, T : R> Result<T>.recover(transform: (exception: Throwable) -> R): Result<R> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> this
        else -> Result.success(transform(exception))
    }
}
  • recoverCatching
// recover 和 runCatching 的结合,相较 recover 多了对 transform 的 try-catcch
public inline fun <R, T : R> Result<T>.recoverCatching(transform: (exception: Throwable) -> R): Result<R> {
    return when (val exception = exceptionOrNull()) {
        null -> this
        else -> runCatching { transform(exception) }
    }
}
  • onFailure
// 该方法类似回调函数,在失败时执行接受的 action 函数
public inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T> {
    contract {
        callsInPlace(action, InvocationKind.AT_MOST_ONCE)
    }
    exceptionOrNull()?.let { action(it) }
    return this
}
  • onSuccess
// 跟 onFailure 类似,成功时执行 action 函数
public inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T> {
    contract {
        callsInPlace(action, InvocationKind.AT_MOST_ONCE)
    }
    if (isSuccess) action(value as T)
    return this
}

5. 总结

如上就是对 Result 的简单介绍,它提供了对操作结果成功或失败的简单封装,我们可以在合适的场景直接使用。

我们也可以看到,Result 只对成功和失败两种情况进行了封装。但开发中一定也会遇到复杂的业务需要,这时我们也可以参考 Result 的定义根据我们真是的业务场景自定义我们自己的 Result 类型(比如:对于网络请求我们可以增加 loading 状态,并封装一个自定义的 Failure 类型更好的处理网络请求相关的异常信息)。

6. 其他

从源码中我们可以看到 Result 的构造方法中有 @SinceKotlin(1.3) 和 @PublishedApi 注解,这是因为 Result 类型在 Kotlin 1.3 版本时就已经定义了,但是那时 Result 类型的使用时有限制的,直到 Kotlin 1.5 它才正式的可以不受限制的使用。

如果想了解更多关于 Kotlin 1.5 之前版本中 Result 类型的相关限制可以查看文档中的相关介绍:Litmitations (legacy)。或我之前的文章:Kotlin Exception - 'kotlin.Result' cannot be used as a return type