У меня есть фильтр для фильтрации неправильных элементов в потоке. В некоторых крайних случаях это может привести к отфильтровыванию всех элементов. Когда это происходит, поток завершается с ошибкой - java.util.NoSuchElementException: reduce over empty stream при бегущем сокращении.

Как обработать этот случай, чтобы вернуть осмысленный ответ?

Я пробовал надзор, например -

val decider: Supervision.Decider = {
    case _                      => Supervision.Stop
    case e : NoSuchElementException => Supervision.stop
  }

RunnableGraph.
toMat(Sink.reduce[Int](_ + _)
.withAttributes(ActorAttributes.supervisionStrategy(decider)))(Keep.both)
.run()

Я также пробовал recover, но ничего не работает.

Мне нужно обработать этот случай, чтобы вернуть осмысленный ответ.

Любая помощь будет оценена по достоинству.

2
noob_learner 31 Янв 2022 в 07:58
Возможно, это обсуждение поможет: discuss.lightbend.com/t/akka-streams -exception-handling/6557 Основная концепция заключается в том, чтобы «обращаться с ошибками как с данными», переносить их вниз по течению и обрабатывать их соответствующим образом. Итак, в вашем случае я предполагаю, что «ошибка» будет случаем isEmpty.
 – 
earthling paul
31 Янв 2022 в 09:45

2 ответа

Просто потому, что вы используете Sink.reduce[Int], вы можете добавить Source, который гарантирует наличие одного элемента 0, и, таким образом, Sink.reduce[Int] будет работать и производить 0 как результат.

Вот пример

val zero          = Source.single(0)
val possiblyEmpty = Source(List[Int](1, 3, 5)).filter(_ % 2 == 0)
val eventualInt   = zero.merge(possiblyEmpty).toMat(Sink.reduce[Int](_ + _))(Keep.right).run()
1
Ivan Stanislavciuc 31 Янв 2022 в 20:11

Возможно, стоит рассмотреть возможность использования Sink.fold вместо Sink.reduce:

val possiblyEmpty = Source(Seq(1, 3, 5)).filter(_ % 2 == 0)
val eventualInt = possiblyEmpty.toMat(Sink.fold[Int](0)(_ + _))(Keep.right).run()

Если нет разумного элемента нуля/идентичности, вы можете иметь что-то более обобщенное в следующих строках:

def reducePossiblyEmpty[T](source: Source[T])(f: (T, T) => T): RunnableGraph[Future[Option[T]]] = {
  val lifted = { (x: Option[T], y: Option[T]) =>
    x.flatMap(a => y.map(f))
  }
  source.map(x => Some(x))
    .concat(Source.single(None))
    .statefulMapConcat[Option[T]] { () =>
      var emptyStream = true
      { x =>
        x match {
          case Some(x) =>
            // element from the given stream
            emptyStream = false
            List(x)

          case None =>
            // given stream completed
            if (emptyStream) {
              List(x)
            } else {
              Nil  // don't emit anything
            }
        }
      }
    }
    .toMat(Sink.reduce[Option[T]](lifted))(Keep.right)
}

Возвращенный граф завершится None, если элементов не было, или Some результата сокращения.

РЕДАКТИРОВАТЬ: вы также можете просто использовать orElse в Source/Flow вместо .concat.statefulMapConcat в приведенном выше примере:

def reducePossiblyEmpty[T](source: Source[T])(f: (T, T) => T): RunnableGraph[Future[Option[T]]] = {
  val lifted = { (x: Option[T], y: Option[T]) =>
    x.flatMap(a => y.map(f))
  }

  source.map(x => Some(x))
    .orElse(Source.single(None))
    .toMat(Sink.reduce[Option[T]](lifted))(Keep.right)
}
0
Levi Ramsey 31 Янв 2022 в 20:14