Alex A.
Alex A.

Reputation: 2603

Dealing with a Future wrapped with Try in scala

I have a repo method that returns a future:

def insert(newUser: User): Future[User]

This method can fail for different reasons, however, I want to be able to catch one of these reasons and rethrow others. If I am simply wrapping the call with Try

Try(userRepository.insert(usr)) match {
      case Success(newUserFuture) => newUserFuture

      case Failure(e) if e.getCause.getMessage.contains("duplicate key") => {
        logger.warn(e.getMessage)
        throw DuplicateEntityException("duplicate user record")
      }

      case Failure(e) => {
        logger.error(e.getMessage)
        throw e
      }
    }

As a result, I am getting Try[Future[User]], and in this situations, neither of the Failures will ever happen, since the Future is successfully created. And at this point, I am not sure how to deal with it. Ideally, I want to get Future[Either[String, User]] out of it (or something similar).

How should I deal with this situation, or am I in completely wrong direction?

EDIT following recommendations Possible solution I've tried something like this:

userRepository.insert(usr).map(newUser => Right(newUser)) recoverWith {
      case e: PSQLException if e.getMessage.contains("duplicate key") =>
        Future.successful(Left("duplicate user record"))
    }

Upvotes: 2

Views: 1481

Answers (3)

Emiliano Martinez
Emiliano Martinez

Reputation: 4133

try something like that:

def futureToFutureTry[T](f: Future[T]): Future[Try[T]] =
   f.map(Success(_)).recover {
     case e: Throwable => {
      Failure(e)
   }
}

...
userRepository.insert(usr) map futureToFutureTry

for checking

def checkOperations[A](operations: List[Try[A]]): Future[List[A]] = {
   Future {
      if (!operations.filter(_.isFailure).isEmpty) {
       // there were some error

      } else {
       // all operations are ok
     }
}

Upvotes: 0

Nagarjuna Pamu
Nagarjuna Pamu

Reputation: 14825

Future will handles the exceptions for you. You need not wrap future code with Try.

Future does the job of the Try as well.

This line of code is not necessary

Try(userRepository.insert(usr))

I hope your insert method is something like this

def insert(newUser: User): Future[User] = Future { doSomeDBCall() }

In the above method. If doSomeDBCall() fails. The exception will be caught by the future safely and will be delivered to caller as Failure(exception). So you need not wrap the Future with a Try

Catch specific exceptions and throw others (propagate others).

You can use recoverWith and handle exceptions and propagate exceptions which you are not interested in.

In the below code SQLException is handled and other exceptions are propagated to the caller.

userRepository.insert(usr).recoverWith { 
 case ex: SQLException => Future.successful(defaultValue)
 case ex => Future.failed(ex)
}

Upvotes: 4

Steve Chaloner
Steve Chaloner

Reputation: 8202

I think you can reverse your approach, and use a guard in the case clean the code up a little. First up is to have userRepository.insert(usr) return a Future[Try[User]], which allows you to just map the future, e.g.

def insert(user: User): Future[Try[User]]

which can then be used like this:

userRepository.insert(user).map {
    case Success(insertedUser) => Right(insertedUser)
    case Failure(ex) if ex.getCause.getMessage.contains("duplicate key") => throw DuplicateEntityException("duplicate user record")
    case Failure(ex) => throw ex
}

The result of this will be a Future[User] that you can attach callbacks to (see http://docs.scala-lang.org/overviews/core/futures.html) to either do something else with the user or deal with the exception.

Upvotes: 1

Related Questions