Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return a specific type from for yield in Scala

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)
  }
}
like image 917
adamtrousdale Avatar asked Dec 08 '25 22:12

adamtrousdale


1 Answers

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.

like image 127
axel22 Avatar answered Dec 11 '25 22:12

axel22



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!