2011-04-19 2 views
3

J'ai mis en œuvre un simple processeur de travail qui traite les sous-jacents dans les futurs (scala.actors.Futures). Ces futurs eux-mêmes peuvent créer plus d'avenir pour le traitement subjobs. Maintenant, si l'un de ces sous-projets renvoie une exception, je souhaite que le processeur de travaux réponde avec un message d'erreur pour ce travail. J'ai une solution de contournement pour découvrir des sous-jobs échoués, mais je ne suis pas sûr si c'est la meilleure solution. Fondamentalement, cela fonctionne comme ceci:Comment gérer les exceptions dans Scala 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)        
    } 
    } 
} 

Le résultat au niveau supérieur est une liste récursive des listes de listes ... de JobResults. Je recherche récursivement la liste pour un résultat échoué et puis renvoie une erreur ou le résultat combiné en fonction des types de résultats. Cela fonctionne mais je me demande s'il existe une solution plus élégante/plus facile pour gérer les exceptions dans les futures?

Répondre

2

futures modernes scala sont comme Either en ce qu'elles contiennent soit un résultat positif ou un Throwable. Si vous visitez à nouveau ce code dans Scala 2.10, je pense que vous trouverez la situation très agréable.

Plus précisément, sur le plan technique que scala.concurrent.Future[T] « est-un » Awaitable[T], mais _.onComplete et Await.ready(_, timeout).value.get présentent tous deux le résultat en tant que scala.util.Try[T], ce qui est beaucoup comme Either[Throwable, T] en ce qu'il est soit le résultat ou une exception.

Bizarrement, _.transform prend deux fonctions de cartographie, un pour T => U et un pour Throwable => Throwable et (à moins que je me manque quelque chose) il n'y a pas de transformateur qui associe l'avenir Try[T] => Try[U]. Future.map vous permettra de transformer un succès en échec en lançant simplement une exception dans la fonction de mappage, mais il l'utilise uniquement pour les succès de l'original Future. Son .recover, de même peut transformer un échec en succès. Si vous voulez être en mesure de changer succès à des échecs et vice-versa, vous aurez besoin de construire quelque chose qui vous était une combinaison de _.map et _.recover ou utiliser d'autre _.onComplete à la chaîne à une nouvelle scala.concurrent.Promise[U] comme ceci:

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 
} 

qui serait utilisé comme ceci:

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() 

qui donnerait quelque chose comme ceci:

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 

(ou vous pourriez « proxénète » Future dans un RichFutureWithFlexibleTransform avec une implicite def et faire flexibleTransform une fonction membre de cela, laissant tomber le fut param et simplement en utilisant this)

(encore mieux serait de prendre Try[T] => Future[U] et l'appeler flexibleFlatMap donc vous pouvez faire des choses asynchrones dans la transformation)

+1

Vous pouvez mentionner le type: ['Try'] (http://www.scala-lang.org/files/archive/nightly/docs/library/index .html # scala.util.Try) – bluenote10

Questions connexes