#kotlin #kotlin-coroutines #flow #kotlin-flow
#kotlin #kotlin-сопрограммы #kotlin-flow
Вопрос:
Я ищу наиболее «чистый» способ реализации следующей логики:
- У меня есть N методов, каждый возвращает поток<Результат<SOME_TYPE>> (тип отличается)
- Я хочу связать эти методы, поэтому, если 1 возвращает результат.Успех, затем вызовите 2nd и так далее.
Наиболее очевидный способ сделать это:
methodA().map { methodAResult ->
when (methodAResult) {
is Result.Success -> {
methodB(methodAResult).map { methodBResult ->
when (methodBResult) {
is Result.Success -> {
methodC(methodAResult).map { methodCResult ->
when (methodCResult) {
is Result.Success -> TODO()
is Result.Failure -> TODO()
}
}
}
is Result.Failure -> TODO()
}
}
}
is Result.Failure -> TODO()
}
}
Но это похоже на хорошо известный «ад обратного вызова». Есть ли у вас какие-либо идеи, как этого избежать?
Ответ №1:
Я считаю, что это можно было бы сгладить с помощью оператора преобразования:
methodA().transform { methodAResult ->
when (methodAResult) {
is Success -> methodB(methodAResult).collect { emit(it) }
is Failure -> TODO()
}
}.transform { methodBResult ->
when (methodBResult) {
is Success -> methodC(methodBResult).collect { emit(it) }
is Failure -> TODO()
}
}.transform { methodCResult ->
when (methodCResult) {
is Success -> TODO()
is Failure -> TODO()
}
}
Комментарии:
1. Да, это похоже на то, что мне нужно, но как насчет результата. Сбой? Как я могу «остановить» цепочку в этом случае? upd: я думаю, что нет никакого способа сделать это. Завтра протестирую ваше решение и надеюсь, что оно будет принято
2. Если вы ничего не выдаете в ветке сбоя, обработка для этого конкретного результата не будет продолжена. Если этого недостаточно, и вы хотите остановить обработку для всех результатов (включая даже успешные) после первого сбоя, вы можете определить некоторое логическое
var thereWasFailureInMethodProcessing = false
значение, установить егоtrue
в ветку сбоя и добавитьif (thereWasFailureInMethodProcessing) return@transform
проверку передwhen
блокировкой.3. Я бы рекомендовал flatMapMerge вместо transform, кажется более естественным. В случае сбоя вы можете вернуть пустой поток или преобразовать результат сбоя в результат правильного типа и вернуть поток с новым результатом. Можно даже заменить блок when вызовом fold .
4. @MichaelKrussel если это возможно, можете ли вы предоставить пример кода, похожий на принятый ответ
Ответ №2:
Небольшая модификация решения, предоставленного Михаил Нафталь
methodA()
.flatMapMerge {
when (it) {
is Result.Success -> methodB(it)
is Result.Failure -> emptyFlow()
}
}.flatMapMerge {
when (it) {
is Result.Success -> methodC(it)
is Result.Failure -> emptyFlow()
}
}.collect {
when (it) {
is Result.Success -> TODO()
is Result.Failure -> TODO()
}
}
Целью flatMap является объединение выходных данных одного потока с другим потоком, поэтому использование flatMap кажется немного более чистым.
Если этот класс результатов имеет map
fold
getOrNull
метод типа , , или, это можно было бы еще немного очистить, а блоки when можно было бы удалить.
Также, если вам нужно распространить сбой для сбора, вы можете заменить вызовы emptyFlow потоком, который просто выводит нужный вам сбой.
Ответ №3:
Плохо flatMap
, что метод все еще не существует.
Но вы можете использовать mapCatching
:
methodA
.mapCatching { a -> methodB(a).getOrThrow() }
.mapCatching { b -> methodC(b).getOrThrow() }
Или создайте свою собственную flatMap
функцию расширения :
fun <T, R> Result<T>.flatMap(block: (T) -> (Result<R>)): Result<R> {
return this.mapCatching {
block(it).getOrThrow()
}
}
methodA
.flatMap { a -> methodB(a) }
.flatMap { b -> methodC(b) }
Ответ №4:
Я считаю, что в этом случае вам, вероятно, следует использовать suspend
функции и составлять их с помощью await()
. Ошибки должны передаваться через исключения, как описано здесь .
Комментарии:
1. вы не можете использовать ожидание в потоке
2. Как я уже писал, для этого ему нужно заменить потоки приостанавливающими функциями.
3. это 2 разные вещи. поток позволяет мне получать «обновления» от «издателей», приостанавливая — используется для выполнения «тяжелой» работы.
4. Вы правы, потоки обычно используются для переменных, а не для функций, поэтому я не заметил. Во-первых, вы, вероятно, должны потерять
onSuccess
onFailure
оператор andwhen
вместо. Мне также трудно понять вариант использования. Если бы вы могли привести пример, возможно, я смогу дать вам лучший ответ.5. допустим, у вас есть вариант использования для добавления нового события. чтобы добавить новое событие, вы должны проверить, доступно ли местоположение события (функция из репозитория местоположений, функция, которая возвращает поток результата <Местоположение>), затем проверяет, доступна ли опция (предопределенного типа события) (функция из репозитория опций, которая возвращает поток результата <Опция>) и так далеевключено. Они не упоминаются в определенном порядке, но должны учитывать результирующий объект (скажем, если проверка местоположения возвращает результат. Сбой, тогда нам не нужно ждать других результатов из разных репозиториев — option и так далее)