I am trying to use a function to call 3 separate APIs asynchronously and collate the responses into a UserDetails
object and return this as part of the function.
However, the issue I am experiencing is that when I explicitly set the return type to Future[UserDetails]
it does not compile as the return from the yield
is not a UserDetails
object and I can't work out how to return what I want from this.
Note I am looking to return a Future[UserDetails]
but the inferred return type is Future[Object]
My code is currently as below:
def getUserDetails(userId: String) = {
usersConnector.getUserById(userId).map {
case Some(user) =>
for {
connections <- connectionsConnector.getAllConnections(user.username) recover {case _ => None}
pendingConnections <- connectionsConnector.getAllPendingConnections(user.username) recover {case _ => None}
userLocation <- userLocationsConnector.getLocationByUsername(user.username) recover {case _ => None}
} yield {
UserDetails(Some(user), connections, pendingConnections, userLocation)
}
case None => UserDetails(None, None, None, None)
}
}
The problem is that the cases in your map function do not return the same type.
The None
case returns UserDetails
. However, the Some
case returns a Future[UserDetails]
. The least upper bound (least common ancestor) of these two types is AnyRef
, that is, Object
. As a result, the map
function transforms the Future[Option[User]]
into a Future[Object]
.
There are two ways to resolve this. First is two use the Future.successful
constructor in the None
case:
...
case None => Future.successful(UserDetails(None, None, None, None))
...
Now, the resulting future has the type Future[Future[UserDetails]]
. You only need to flatten it at this point:
...
}.flatMap(x => x) // add at the end
The second, more elegant way is to embed everything into the for-comprehension:
def getUserDetails(userId: String) = {
def detailsFor(user: Option[User]) = user match {
case Some(user) =>
for {
connections <- connectionsConnector.getAllConnections(user.username) recover {case _ => None}
pendingConnections <- connectionsConnector.getAllPendingConnections(user.username) recover {case _ => None}
userLocation <- userLocationsConnector.getLocationByUsername(user.username) recover {case _ => None}
} yield {
UserDetails(Some(user), connections, pendingConnections, userLocation)
}
case None => Promise.successful(UserDetails(None, None, None, None))
}
for {
user <- usersConnector.getUserById(userId)
userDetails <- detailsFor(user)
} yield userDetails
}
EDIT:
See Łukasz's comment below about starting futures asynchronously in a for-comprehension.