2011-04-19 84 views
3

我實現了一個簡單的作業處理器,處理期貨(scala.actors.Futures)中的subjobs。這些期貨本身可以爲處理subjobs創造更多的期貨。現在,如果其中一個子作業引發異常,我希望作業處理器回覆該作業的錯誤消息。我有一個解決方案來發現失敗的subjobs,但我不確定這是否是最好的解決方案。基本上,它的工作原理是這樣的:如何處理斯卡拉期貨中的例外情況?

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

現代斯卡拉期貨是在Either它們包含一個成功的結果或Throwable。如果你重新訪問scala 2.10中的這段代碼,我想你會發現情況相當愉快。

具體地,技術上scala.concurrent.Future[T]僅「是一個」 Awaitable[T],但_.onCompleteAwait.ready(_, timeout).value.get都呈現其結果作爲scala.util.Try[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 

(或者你可以「皮條客」 FutureRichFutureWithFlexibleTransform有一個隱含的清晰度,使flexibleTransform的一個成員函數,丟棄fut PARAM和簡單的使用this

(甚至更好的是採取Try[T] => Future[U]並調用它flexibleFlatMap所以你可以做轉換異步的東西)

+1

你可能想提一下這個類型:['Try'](http://www.scala-lang.org/files/archive/nightly/docs/library/index的.html#scala.util.Try) – bluenote10 2014-05-01 15:27:10