如何在错误结果上打破RxJava链?

时间:2019-01-26 14:04:51

标签: android rx-java reactive-programming rx-java2 rx-kotlin

我继承了此代码库,该代码库使用RxJava2和kotlin以及用于API调用的相当特殊的Result模式。也就是说,所有API调用都将返回带有Result对象(如下所示的密封的Success和Error类型的密封类)的Singles,即

sealed class Result<T, E> {
    data class Success<T, E>(
            val data: T
    ): Result<T, E>()

    data class Error<T, E>(
            val error: E
    ): Result<T, E>()
}

现在,我正在尝试将这些API链接在一起,但是需要在其中的第一个Result.Error处终止链接,如果没有,请继续。

我能想到的唯一方法是压缩所有Singles,然后具有一个拉链函数,该函数检查每个参数的类型,并返回Result.Error()及其遇到的第一个错误。即类似

Singles.zip(
    repo1.makeCall1(arg),
    repo1.makeCall2(arg2),
    repo2.makeCall1(arg3)
) { result1, result2, result3 ->
    val data1 = when (result1) {
        is Result.Error -> return@zip Result.Error(result1.error)
        is Result.Success -> result1.data
    }
    val data2 = when (result2) {
        is Result.Error -> return@zip Result.Error(result2.error)
        is Result.Success -> result2.data
    }
    val data3 = when (result3) {
        is Result.Error -> return@zip Result.Error(result3.error)
        is Result.Success -> result3.data
    }

    return@zip Result.Success(MergedData(data1, data2, data3))
}

可以工作,但看起来确实很奇怪(使用这种巨大的ass拉链方法,感觉像是代码的味道)。另外,不允许我在最后一个方法(检查结果是否为成功/错误)之后再进行其他链接。

我认为能够链接这些调用并在出现第一个错误时终止将更具可读性,但是我不知道有足够的Rx可以做到这一点。是否有运营商或方法可以帮助改善这一点?

3 个答案:

答案 0 :(得分:1)

通过反转代码库已经执行的操作,您可以得到原始的Single行为。

创建转换器,该转换器将从api调用中提取数据,或者在出错时抛出错误。第一个错误将终止zip

public <T, E extends Throwable> SingleTransformer<Result<T, E>, T> transform() {
    return source -> source.flatMap(result -> {
        if (result instanceof Result.Success) 
            return Single.just(((Success<T, E>) result).getData());
          else
            return Single.error(((Error<T, E>) result).getError());
    });
}

repo.makeCall(arg).compose(transform())

一起使用

希望有帮助。

答案 1 :(得分:1)

开箱即用,RxJava将“中止第一个错误”,因为Observable和Single(类似于Task / Future / Promise)具有“ monadic品质”。但是,由于Result<*, *>明确地使错误在“成功”路径上进行处理以避免流中止,因此,我们可能需要考虑一种不同于让Rx进入终端事件的路由-因为现有代码希望它位于成功之路。终止事件应针对“世界正在终结”异常,而不是我们实际上期望并可以处理的异常。


我有一些想法,但我认为您唯一可以做的是减少执行此操作所需的行数,而不是完全删除它。

从技术上讲,我们正在尝试重新实现Either<E, T> monad here from Arrow,但要知道,我们可以通过一些技巧来减少行数:

sealed class Result<T, E>(
    open val error: E? = null,
    open val data: T? = null
) {
    data class Success<T>(
        override val data: T
    ): Result<T, Nothing?>()

    data class Error<E>(
        override val error: E
    ): Result<Nothing?, E>()
}

fun <E> E.wrapWithError(): Result.Error<E> = Result.Error(this) // similar to `Either.asLeft()`
fun <T> T.wrapWithSuccess(): Result.Success<T> = Result.Success(this)  // similar to `Either.asRight()`

fun blah() {
    Singles.zip(
        repo1.makeCall1(arg),
        repo1.makeCall2(arg2),
        repo2.makeCall1(arg3)
    ) { result1, result2, result3 ->
        val data1 = result1.data ?: return@zip result1.error.wrapWithError()
        val data2 = result2.data ?: return@zip result2.error.wrapWithError()
        val data3 = result3.data ?: return@zip result3.error.wrapWithError()

        Result.Success(MergedData(data1, data2, data3))
    }
}

答案 2 :(得分:0)

您如何看待此代码块:

Single.zip(
        Single.just(Result.Error(error = 9)),
        Single.just(Result.Success(data = 10)),
        Single.just(Result.Success(data = 11)),
        Function3<Result<Int, Int>, Result<Int, Int>, Result<Int, Int>, List<Result<Int, Int>>> { t1, t2, t3 ->
            mutableListOf(t1, t2, t3)
        })
        .map { list ->
            list.forEach {
                if (it is Result.Error){
                    return@map it
                }
            }
            return@map Result
        } // or do more chain here.
        .subscribe()

我将结果合并为list,然后将其映射到您的预期结果。它更容易阅读。