Как разорвать цепочку RxJava при ошибке Result? - PullRequest
0 голосов
/ 26 января 2019

Я унаследовал эту кодовую базу, которая использует RxJava2 и kotlin с довольно своеобразным шаблоном Result для вызовов API.т.е. все вызовы API возвращают Singles с объектом Result (который является запечатанным классом типов Success и Error, как показано ниже), т.е.

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 в нем и продолжить, если нет.

Единственный способ, которым я могу думать, - это сжать все синглы, а затем иметь функцию молнии, которая проверяет типкаждый параметр и возвращает 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))
}

, которое работает, но выглядит действительно странно (и похоже на запах кода при использовании этого метода с огромной задницей на молнии).Также не позволяет мне связывать что-либо еще после последнего метода (который проверяет, является ли Результат успехом / ошибкой).

Я чувствую, что было бы намного удобнее читать цепочку этих вызовов и завершатьна первую ошибку, но я не знаю достаточно Rx, чтобы сделать это.Есть оператор или подход, который может помочь сделать это лучше?

Ответы [ 3 ]

0 голосов
/ 26 января 2019

Из коробки RxJava "прерывал бы при первой ошибке", потому что Observable и Single (что сродни Task / Future / Promise) имеют "монадические качества".Но так как Result<*, *> явно обрабатывает ошибки на пути «успеха», чтобы избежать прерывания потока, мы могли бы рассмотреть другой путь, чем пропуск Rx к терминальным событиям - потому что существующий код ожидает, что он будет на пути успеха,Терминальные события должны быть для исключений «конец света», а не для тех, которые мы на самом деле ожидаем и можем обработать.


У меня были некоторые идеи, но я думаю, что единственное, что вы можете сделать, это уменьшитьколичество строк, которое требуется, чтобы сделать это вместо того, чтобы вычеркнуть его.

Технически мы пытаемся повторно реализовать монаду Either<E, T> здесь из стрелки , но знаячто, мы можем уменьшить количество строк с помощью некоторых трюков:

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))
    }
}
0 голосов
/ 26 января 2019

Что вы думаете об этом блоке кода:

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, затем сопоставляю их с ожидаемым результатом. Гораздо легче читать.

0 голосов
/ 26 января 2019

Вы можете получить оригинальное 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())

Надеюсь, это поможет.

...