22
votes

I'm using Scala, Play Framework 2.1.x, and reactivemongo driver.

I have an api call :

def getStuff(userId: String) = Action(implicit request => {
    Async {
      UserDao().getStuffOf(userId = userId).toList() map {
        stuffLst => Ok(stuffLst)
      } 
    }
})

It works fine 99% of the time but it may fail sometimes (doesn't matter why, that's not the issue).

I wanted to recover in a case of an error so i added:

recover { case _ => BadRequest("")}

But this does not recover me from errors.
I tried the same concept on the scala console and it worked:

import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
var f = future { throw new Exception("") } map {_ => 2} recover { case _ => 1}
Await.result(f, 1 nanos)

This returns 1 as expected.
I currently wrapped the Async with:

try{
  Async {...}
} catch {
  case _ => BadRequest("")
} 

And this catches the errors.

I went over some Scala's Future docs on the net and I'm baffled why recover did not work for me.

Does anyone know why? What do I miss to sort it out?

2

2 Answers

46
votes

Why it fails actually matters 100%. If we spread the code over a number of lines of code, you'll understand why:

def getStuff(userId: String) = Action(implicit request => {
  Async {
    val future = UserDao().getStuffOf(userId = userId).toList()
    val mappedFuture = future.map {
      stuffLst => Ok(stuffLst)
    }
    mappedFuture.recover { case _ => BadRequest("")}
  }
})

So, UserDao().getStuffOf(userId = userId).toList() returns you a future. A future represents something that may not have happened yet. If that thing throws an exception, you can handle that exception in recover. However, in your case, the error is happening before the future is even being created, the UserDao().getStuffOf(userId = userId).toList() call is throwing an exception, not returning a future. So the call to recover the future will never be executed. It's equivalent to doing this in the Scala repl:

import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
var f = { throw new Exception(""); future { "foo" } map {_ => 2} recover { case _ => 1} }
Await.result(f, 1 nanos) }

Obviously that doesn't work, since you never created the future in the first place beacuse the exception was thrown before the code to create the future happened.

So the solution is to either wrap your call to UserDao().getStuffOf(userId = userId).toList() in a try catch block, or find out why it's failing in whatever method you're calling, and catch the exception there, and return a failed future.

4
votes

If you have a later version of Play eg 2.2.x, you can do this:

def urlTest() = Action.async {
    val holder: WSRequestHolder = WS.url("www.idontexist.io")
    holder.get.map {
      response =>
        println("Yay, I worked")
        Ok
    }.recover {
      case _ =>
        Log.error("Oops, not gonna happen")
        InternalServerError("Failure")
    }
}