Как бороться с исключениями в Scala Futures? - PullRequest
3 голосов
/ 19 апреля 2011

Я реализовал простой процессор заданий, который обрабатывает подзадачи внутри фьючерсов (scala.actors.Futures).Сами эти фьючерсы могут создавать больше фьючерсов для обработки подзадач.Теперь, если одно из этих вложенных заданий выдает исключение, я хочу, чтобы обработчик заданий ответил сообщением об ошибке для этого задания.У меня есть обходное решение для обнаружения неудачных подзадач, но я не уверен, что это лучшее решение.В основном это работает так:

sealed trait JobResult
case class SuccessResult(content: String) extends JobResult
case class FailedResult(message: String) extends JobResult

for(subjob <- subjobs) yield {
  future {
    try {
          SuccessResult(process(subjob))
    } catch {
      case e:Exception => FailedResult(e.getMessage)                              
    }
  }
}

Результатом на верхнем уровне является рекурсивный список списков списков ... JobResults.Я рекурсивно ищу в Списке ошибочный результат, а затем возвращаю ошибку или объединенный результат в зависимости от типов результатов.Это работает, но мне интересно, есть ли более элегантное / простое решение для работы с исключениями во фьючерсах?

Ответы [ 2 ]

5 голосов
/ 19 апреля 2011

То, как вы делаете это сейчас, по сути то, для чего был разработан scala.Either.См. http://www.scala -lang.org / api / current / scala / Either.html

2 голосов
/ 16 марта 2014

Современные фьючерсы на scala похожи на Either в том смысле, что содержат либо успешный результат, либо Throwable. Если вы повторно посетите этот код в Scala 2.10, я думаю, вы найдете ситуацию довольно приятной.

В частности, scala.concurrent.Future [T] технически только "is-a" Awaitable[T], но _.onComplete и Await.ready(_, timeout).value.get оба представляют свой результат как scala.util . Попробуйте [T] , что очень похоже на Either[Throwable, T] в том смысле, что это либо результат, либо исключение.

Как ни странно, _.transform принимает две функции отображения, одну для T => U и одну для Throwable => Throwable, и (если я что-то упустил) нет преобразователя, который отображал бы будущее как Try[T] => Try[U]. Future .map позволит вам превратить успех в неудачу, просто выбрасывая исключение в функции отображения, но оно использует его только для успехов исходного Future. Его .recover также может превратить неудачу в успех. Если вы хотите иметь возможность изменять успехи на неудачи и наоборот, вам нужно было бы создать что-то самостоятельно, представляющее собой комбинацию _.map и _.recover, или же использовать _.onComplete, чтобы связать с новым scala.concurrent.Promise [U] примерно так:

import scala.util.{Try, Success, Failure}
import scala.concurrent.{Future, Promise}
import scala.concurrent.ExecutionContext

def flexibleTransform[T,U](fut: Future[T])(f: Try[T] => Try[U])(implicit ec: ExecutionContext): Future[U] = {
  val p = Promise[U]
  fut.onComplete { res =>
    val transformed = f(res)
    p.complete(transformed)
  }
  p.future
}

, который будет использоваться так:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration.Duration.Inf

def doIt() {
  val a: Future[Integer] = Future {
    val r = scala.util.Random.nextInt
    if (r % 2 == 0) {
      throw new Exception("we don't like even numbers")
    } else if (r % 3 == 0) {
      throw new Exception("we don't like multiples of three")
    } else {
      r
    }
  }

  val b: Future[String] = flexibleTransform(a) {
    case Success(i) =>
      if (i < 0) {
        // turn negative successes into failures
        Failure(new Exception("we don't like negative numbers"))
      } else {
        Success(i.toString)
      }
    case Failure(ex) =>
      if (ex.getMessage.contains("three")) {
        // nevermind about multiples of three being a problem; just make them all a word.
        Success("three")
      } else {
        Failure(ex)
      }
  }

  val msg = try {
    "success: " + Await.result(b, Inf)
  } catch {
    case t: Throwable =>
      "failure: " + t
  }
  println(msg)
}

for { _ <- 1 to 10 } doIt()

, что даст что-то вроде этого:

failure: java.lang.Exception: we don't like even numbers
failure: java.lang.Exception: we don't like negative numbers
failure: java.lang.Exception: we don't like negative numbers
success: three
success: 1756800103
failure: java.lang.Exception: we don't like even numbers
success: 1869926843
success: three
failure: java.lang.Exception: we don't like even numbers
success: three

(или вы можете "pimp" Future в RichFutureWithFlexibleTransform с неявным определением и сделать flexibleTransform функцией-членом этого, удалив параметр fut и просто используя this)

(еще лучше было бы взять Try[T] => Future[U] и назвать его flexibleFlatMap, чтобы вы могли выполнять асинхронные операции в преобразовании)

...