Цепочка потоков kotlin зависит от состояния результата

#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 оператор and when вместо. Мне также трудно понять вариант использования. Если бы вы могли привести пример, возможно, я смогу дать вам лучший ответ.

5. допустим, у вас есть вариант использования для добавления нового события. чтобы добавить новое событие, вы должны проверить, доступно ли местоположение события (функция из репозитория местоположений, функция, которая возвращает поток результата <Местоположение>), затем проверяет, доступна ли опция (предопределенного типа события) (функция из репозитория опций, которая возвращает поток результата <Опция>) и так далеевключено. Они не упоминаются в определенном порядке, но должны учитывать результирующий объект (скажем, если проверка местоположения возвращает результат. Сбой, тогда нам не нужно ждать других результатов из разных репозиториев — option и так далее)