我继承了此代码库,该代码库使用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可以做到这一点。是否有运营商或方法可以帮助改善这一点?
答案 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
,然后将其映射到您的预期结果。它更容易阅读。