Blankman
Blankman

Reputation: 267320

How to handle futures that return options in a for comprehension

My services are returning Future[Option[Entity]] and I am currently doing this:

val user = userService.getById(1)
val company = userService.getById(1)

if(user.get.companyId == company.get.id) {
   save(...)

   if(...) {
      blahService.boo();   
   }

   return ...
} else 
{
  ...
  return ...
}

Now if I refactor this to a for-comprehension, how do I handle the else clause? Do I return something from the for-comp and then use that like the 'else' clause?

Also, how do I get rid of the .get from inside the for compr?

for {
  userOption <- userService.getById(1)
  companyOption <- cService.getById(2)
  if (userOption.get.companyId == companyOption.get.id)
} {
      ..
      ..
      saveUser(userOption.get, companyOption.get)
      return ..
}
  1. So I am trying to get rid of my .get calls when using the for-comp., how?
  2. How do I handle the else clause that I initially had?
  3. Is it bad practise to have a for-comp inside of another for-comp?
  4. In the above code I have a if(...) and then I call blahService.boo(). If this is returnign a future, how do I call this without letting the code below execute before this? Since it is a 'if' it is confusing me.

Upvotes: 1

Views: 189

Answers (3)

acjay
acjay

Reputation: 36671

When it comes to mixing Futures with control flow, I highly recommend scala-async. I find it to be almost strictly better than map or for-comprehensions for Futures. There are some limitation to what control flow structures you can use, but for your situation, it would work great:

async {
  val userOption = await(userService.getById(1))
  val companyOption = await(cService.getById(2))

  (userOption, companyOption) match {
    case (Some(user), Some(company)) => // happy path
    case _ => // sad path
  }
}

This whole async {...} block is of type Future[A], where A is determined by the branches of the match.

Upvotes: 1

danielnixon
danielnixon

Reputation: 4268

Here's a solution using Scalaz's OptionT:

import scalaz.OptionT.optionT

(for {
  user <- optionT(userService.getById(1))
  company <- optionT(cService.getById(2))
  if (user.companyId == company.id)
} yield {
  saveUser(user, company)
}).run

See e.g. this discussion for more: http://loicdescotte.github.io/posts/scala-compose-option-future/

Upvotes: 2

lolski
lolski

Reputation: 17511

If you are restricted to vanilla Scala,

val x = for {
  userOption    <- userService.getById(1)
  companyOption <- cService.getById(2)

} yield {
  for {
    user    <- userOption
    company <- companyOption
  } yield {
    if (user.id == company.id) { ... }
    else { ... }
  }
}
x

then this is as simple as you can get.

Note that the resulting Future will fail if one of the two calls returns a Failure. Also, the resulting Option will be None if one of the call returns None. This comprehension is basically good only for "happy path" code, and might not be sufficient for cases where different actions need to be taken for every different branch.

Upvotes: 0

Related Questions