Chris
Chris

Reputation: 507

Dealing with errors using idiomatic Scala

I'm writing an HTTPS service for a chat bot and find myself dealing with a lot of Futures and Options. Usually if an Option returns None or a Future fails I want to log the exception and reset the user back to the start. Here's a toy example of how I accomplish this:

(for {
  user <- userService.retrieve(userId)
  userPet <- Future(user.userPet.get)
  _ <- sendTextAsJson(s"You're holding a $userPet!")
} yield {}).recover {
  case ex: Exception =>
    log.error(ex.toString)
    fail
}

This works fine but it feels a little weird to wrap things in Future just so their exceptions are swallowed and dealt with in the recover block. It also feels weird to include an empty yield block. Is there a better way?

Upvotes: 0

Views: 87

Answers (4)

Jono
Jono

Reputation: 103

Looking at your sendTextAsJson and fail functions you seem to want to side-effect when the future completes. I would not use map after you perform the Option.get and instead look at Futures onComplete method to either handle the success of the future or handle the exception throw. I am not sure how this way and recover are different though so double check that. I did like @sascha10000 link to the scala doc futures. Its been a long time since I read that but its a good resource from what I remember

Example implementation of your code with onComplete:

import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global

object UserFuture extends App {

  def fail = println("failed")

  def sendTextAsJson(message: String): Unit = println(message)

  case class User(userPet: Option[String])

  object userService {

    def userPet = Some("dog")

    val someUser = User(userPet)

    def retrieve(userId: Int): Future[User] = Future {
      someUser
    }
  }

  def getPet(userId: Int): Unit = {
    userService.retrieve(userId)
      .map(user => user.userPet.get)
      .onComplete {
        case Success(userPet) => sendTextAsJson(s"You're holding a $userPet!")
        case Failure(ex) => println(ex.toString); fail
      }
  }

  getPet(1)

  Thread.sleep(10000) // I forgot how to use Await. This is just here to be able to make sure we see some printouts in the console.

}

Upvotes: 0

C4stor
C4stor

Reputation: 8026

I don't think it's too bad tbh, assuming userService.retrieve() returns a Future in the first place. I'd personnally rather use map in this case to make things a bit more explicit :

val futureMsg = userService.retrieve(userId)
.map(user => sendTextAsJson(s"You're holding a ${user.userPet.get}!")
.recover {
  case NonFatal(ex) => //Look it up ;)
    log.error(ex.toString)
    fail
}

You now have Future[Unit] to do whatever you want with.

Upvotes: 0

Zolt&#225;n
Zolt&#225;n

Reputation: 22156

I agree with you that that's an awkward use of a for-comprehension. This is how I would write this:

import scala.util.control.NonFatal

userService.retrieve(userId)
  .map(_.userPet.get)
  .map(userPet => s"You're holding a $userPet!")
  .flatMap(sendTextAsJson)
  .recover {
    case NonFatal(ex) =>
      log.error(ex.toString)
      fail
  }

Upvotes: 0

sascha10000
sascha10000

Reputation: 1230

What you basically do is using onSuccess or onFailure to retrieve the futures result. What you also might try is Try.

There is an example of the underlying functionality. http://www.scala-lang.org/api/2.9.3/scala/util/Try.html

I might suggest you this article: http://docs.scala-lang.org/overviews/core/futures.html I can't summarize whats stated there in a few sentences. But if you look to the table of contents on the right side, the point Futures explains what happens and how to handle it is stated under Excepetions. Thats the idiomatic way.

Upvotes: 1

Related Questions