As we develop more and more code in Scala, we are trying to stick to a few key principles. One of these is to avoid throwing Exceptions at all costs. In pretty much every case, this can be avoided by using Options or Eithers, however, sometimes it gets a little messy.
One such example we came across recently is when you are dealing with Futures, in particular doing something like a web request.
For example: Let's say you want to request some data from an API using Play's WebService library (or any HTTP library). Once you've got the result back you want to check whether the response was a 200 or not, if so, you want to parse the JSON body. Finally, this all should be happening in side a Future so that it doesn't block your code.
In this case, the initial Future may succeed, but contain a 400, in which case you still want to fail.
Here is a naive implementation:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def getUserData(userId: Int): Future[JsValue] = { | |
val request = WS.url("http://example.com/user/1").withHeaders("Content-Type" -> "application/json").get() | |
request.map { response => | |
if (response.status == 200) { | |
response.json | |
} else { | |
throw new Exception("Error received. Status: ${response.status} Body: ${response.json}") | |
} | |
} | |
} |
So we can get around this using Future.failed, however, as we are failing the future once the response has been received (that is the initial request future has now completed) we need to use flatMap instead of map. Otherwise our failure case would be Future[Future[JsValue]] and our success case would be Future[JsValue].
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def getUserData(userId: Int): Future[JsValue] = { | |
val request = WS.url("http://example.com/user/1").withHeaders("Content-Type" -> "application/json").get() | |
request.flatMap { response => | |
if (response.status == 200) { | |
Future(response.json) | |
} else { | |
Future.failed(new Exception("Error received. Status: ${response.status} Body: ${response.json}")) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def predicate[T](condition: => Boolean)(success: => T)(error: Exception): Future[T] = { | |
if(condition) Future(success) else Future.failed(error) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def getUserData(userId: Int): Future[JsValue] = { | |
for { | |
response <- WS.url("http://example.com/user/1").withHeaders("Content-Type" -> "application/json").get() | |
responseJson <- predicate(response.status == 200)(response.json)("Error received. Status: ${response.status} Body: ${response.json}") | |
} yield responseJson | |
} |
No comments:
Post a Comment