2015-08-30 90 views
12

我想在我的play scala web應用程序中進行錯誤處理。錯誤處理Scala:未來理解

我的應用程序與數據庫進行通信以獲取一些行,它遵循以下流程。

  1. 首先調用數據庫獲取一些數據
  2. 使用在第一次調用的數據從數據庫
  3. 窗體使用來自最後兩個調用數據庫接收到的數據的響應獲取其他數據。

下面是我的僞代碼。

def getResponse(name: String) 
     (implicit ctxt: ExecutionContext): Future[Response] = { 
    for { 
     future1 <- callFuture1(name) 
     future2 <- callFuture2(future1.data) 
     future3 <- callFuture3(future1.data, future2.data) 
    } yield future3 
    } 

上述理解中的每個方法都返回一個未來,這些方法的簽名如下。

private def callFuture1(name: String) 
    (implicit ctxt: ExecutionContext): Future[SomeType1] {...} 

private def callFuture2(keywords: List[String]) 
    (implicit ctxt: ExecutionContext): Future[SomeType2] {...} 

private def callFuture3(data: List[SomeType3], counts: List[Int]) 
    (implicit ctxt: ExecutionContext): Future[Response] {...} 

怎樣對待錯誤/故障處理,在下列情況下

  • 當callFuture1無法從數據庫中提取數據。我想返回 錯誤消息的相應錯誤響應。由於callFuture2 僅在callFuture1之後執行。我不想執行 callFuture2,如果callFuture1失敗/錯誤並且想立即返回 錯誤消息。 (對於callFuture2和 callFuture3同樣的事情)

- 編輯 -

我試圖返回從GETRESPONSE()方法,當任一callFuture的失敗,而不是繼續執行後續的相應的錯誤信息futureCalls。

我嘗試以下,根據彼得Neyens答案,但給了我一個運行時錯誤..

def getResponse(name: String) 
     (implicit ctxt: ExecutionContext): Future[Response] = { 
    for { 
     future1 <- callFuture1(name) recoverWith { 
     case e:Exception => return Future{Response(Nil,Nil,e.getMessage)} 
     } 
     future2 <- callFuture2(future1.data) 
     future3 <- callFuture3(future1.data, future2.data) 
    } yield future3 
    } 

運行時錯誤,我得到

ERROR] [08/31/2015 02:09:45.011] [play-akka.actor.default-dispatcher-3] [ActorSystem(play)] Uncaught error from thread [play-akka.actor.default-dispatcher-3] (scala.runtime.NonLocalReturnControl) 
[error] a.a.ActorSystemImpl - Uncaught error from thread [play-akka.actor.default-dispatcher-3] 
scala.runtime.NonLocalReturnControl: null 

回答

18

您可以使用Future.recoverWith功能,自定義如果Future失敗,則爲例外。

val failed = Future.failed(new Exception("boom")) 
failed recoverWith { 
    case e: Exception => Future.failed(new Exception("A prettier error message", e) 
} 

這將導致一個稍微難看的理解:

for { 
    future1 <- callFuture1(name) recoverWith { 
       case npe: NullPointerException => 
       Future.failed(new Exception("how did this happen in Scala ?", npe)) 
       case e: IllegalArgumentException => 
       Future.failed(new Exception("better watch what you give me", e)) 
       case t: Throwable => 
       Future.failed(new Exception("pretty message A", t)) 
      } 
    future2 <- callFuture2(future1.data) recoverWith { 
       case e: Exception => Future.failed(new Exception("pretty message B", e)) 
      } 
    future3 <- callFuture3(future1.data, future2.data) recoverWith { 
       case e: Exception => Future.failed(new Exception("pretty message C", e)) 
      } 
} yield future3 

請注意,您還可以定義自己的異常,而不是使用Exception,如果你需要的不僅僅是一個錯誤添加更多信息信息。

如果你不想細粒度控制,取決於在Throwable設置不同的錯誤信息失敗Future(像callFuture1),你可以使用一個隱含的類較爲簡單設置自定義錯誤消息充實Future

implicit class ErrorMessageFuture[A](val future: Future[A]) extends AnyVal { 
    def errorMsg(error: String): Future[A] = future.recoverWith { 
    case t: Throwable => Future.failed(new Exception(error, t)) 
    } 
} 

,你可以使用這樣的:

for { 
    future1 <- callFuture1(name) errorMsg "pretty A" 
    future2 <- callFuture2(future1.data) errorMsg "pretty B" 
    future3 <- callFuture3(future1.data, future2.data) errorMsg "pretty C" 
} yield future3 

在這兩種情況下,使用errorMsgrecoverWith直接,Y ou仍然依賴Future,因此如果Future出現故障,則不會執行以下Futures,您可以直接使用故障Future內的錯誤消息。

您沒有指定如何處理錯誤消息。例如,如果您想使用錯誤消息創建不同的Response,則可以使用recoverWithrecover

future3 recover { case e: Exception => 
    val errorMsg = e.getMessage 
    InternalServerError(errorMsg) 
} 
+0

感謝您的回答。這很有幫助。我仍然不確定這是否解決了我的問題。 1)使用方法1,(不使用隱式類),當我恢復Future.failed(新的異常()),引發異常,我的執行不會繼續。我試圖在任何futurecall失敗時返回一個適當的ResponseMessage,而不繼續後續futurecalls(我將更新問題以使其更清楚) – konquestor

+0

Future.fail(new Exception(...))導致執行拋出一個例外。相反,我想返回一個適當的錯誤消息,當futurecall失敗/錯誤。 – konquestor

+0

嘗試像我的答案中的最後一個代碼示例。 –

1

future1future2future3拋出Throwable例外分別命名爲Future1ExceptionFuture2ExceptionFuture3Exception。然後你可以從getResponse()方法返回適當的錯誤Response如下:

def getResponse(name: String) 
      (implicit ctxt: ExecutionContext): Future[Response] = { 
    (for { 
    future1 <- callFuture1(name) 
    future2 <- callFuture2(future1.data) 
    future3 <- callFuture3(future1.data, future2.data) 
    } yield future3).recover { 
    case e: Future1Exception => 
     // build appropriate Response(...) 

    case e: Future2Exception => 
     // build appropriate Response(...) 

    case e: Future3Exception => 
     // build appropriate Response(...) 
    } 
} 

根據文檔Future.recover

創建一個新的未來,將處理任何匹配的拋出,這 未來可能包含。