2015-08-30 16 views
12

Chcę przeprowadzić obsługę błędów w mojej aplikacji internetowej scala.Obsługa błędów Scala: Future For Comprehension

Moja aplikacja rozmawia z bazą danych, aby pobrać kilka wierszy, następuje następujący przepływ.

  1. Pierwsze zaproszenie do db pobrać jakieś dane
  2. wykorzystywać dane w pierwszym zaproszeniu do pobierania innych danych z db
  3. Formularz odpowiedzi na podstawie danych otrzymanych od dwóch ostatnich połączeń DB.

Poniżej znajduje się mój pseudokod.

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 
    } 

Każda metoda w powyższym rozumieniu zwraca przyszłość, sygnatura tych metod jest przedstawiona poniżej.

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] {...} 

Jak zrobię obsługi błędów/awarii, w następującej sytuacji

  • Kiedy callFuture1 nie trafia do pobierania danych z bazy danych. Chcę zwrócić odpowiednią odpowiedź o błędzie z komunikatem o błędzie. Ponieważ callFuture2 zostaje wykonany dopiero po wywołaniu callFuture1. Nie chcę, aby wykonać wywołanie callFuture2, jeśli callFuture1 nie powiodło się/błąd i chciałby natychmiast zwrócić komunikat o błędzie . (To samo dla callFuture2 i callFuture3)

--edit--

Próbuję zwrócić właściwą odpowiedź błędu z getResponse() metoda, gdy którekolwiek z callFuture zawiedzie i nie przystąpić do kolejnego futureCalls.

Próbowałem następujących, na podstawie Peter Neyens odpowiedź, ale dał mi błąd runtime ..

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 
    } 

Runtime error ja dostać

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 

Odpowiedz

18

Można użyć funkcji Future.recoverWith, aby dostosować wyjątek, jeśli nie udało się Future.

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

Spowoduje to nieco brzydsze do zrozumienia:

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 

Należy pamiętać, że można również definiować własne wyjątki używać zamiast Exception, jeśli chcesz dodać więcej informacji niż tylko błędem wiadomość.

Jeśli nie chcesz dobrze kontrola drobnoziarnisty ustawić inny komunikat o błędzie w zależności od Throwable w zawiodły Future (jak z callFuture1), można wzbogacić Future stosując niejawny klasę ustawić niestandardowy komunikat o błędzie nieco prostsze:

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

które można użyć jak:

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 

W obu przypadkach przy użyciu errorMsg lub recoverWith bezpośrednio, Y Nadal polegać na Future, więc jeśli Future nie powiedzie się, następujące Futures nie zostaną wykonane i można bezpośrednio użyć komunikatu o błędzie wewnątrz nieudanego Future.

Nie określono sposobu obsługi komunikatów o błędach. Jeśli na przykład chcesz użyć komunikatu o błędzie, aby utworzyć inny Response, możesz użyć recoverWith lub recover.

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

Dzięki za tę odpowiedź. to było pomocne. nadal nie jestem pewien, czy to rozwiąże mój problem. 1) Korzystając z podejścia 1, (bez użycia domyślnej klasy), po odzyskaniu Future.failed (new Exception()), zgłoszony wyjątek i moje wykonanie nie jest kontynuowane. Próbuję zwrócić odpowiednią wartość ResponseMessage, gdy któraś z przyszłych awarii nie powiedzie się, bez przechodzenia do kolejnych przyszłych kroków (zaktualizuję to pytanie, aby było bardziej zrozumiałe) – konquestor

+0

Future.fail (nowy wyjątek (...)) powoduje wykonanie rzutu wyjątek. Zamiast tego chcę Zwrócić odpowiedni komunikat o błędzie, gdy coś nie powiedzie się/błędy w przyszłości. – konquestor

+0

Spróbuj czegoś podobnego do ostatniego przykładu kodu w mojej odpowiedzi. –

1

Say future1, future2 i future3 rzut Throwable wyjątki nazwie Future1Exception, Future2Exception i Future3Exception odpowiednio. Potem można wrócić odpowiedni błąd Response z getResponse() metody następująco:

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

Według dokumentacji Future.recover

Tworzy nową przyszłość, która będzie obsługiwać dowolny pasujący Throwable że ta przyszłość może zawierać .