I started migrating my Scala 2.11 code base to Scala 2.12. In my application I had a method that looked like this:
Future {
someMethodReturningTry()
} onSuccess {
case Success(result) => processResult()
case Failure(reason) => log.error(s"Couldn't do it: ${reason.getMessage}")
}
Now, if you compile this with Scala 2.12, you'll get:
method onSuccess in trait Future is deprecated (since 2.12.0): use foreach or onComplete instead (keep in mind that they take total rather than partial functions)
So I started exploring how I could solve this in an elegant way.
The someMethodReturningTry() method really should return a Try[], since it involves parsing some text structure and that might fail, so I prefer to keep the return type for that method the same.
The best I could think of is
Future {
someMethodReturningTry()
} flatMap {
case Success(result) => Future.successful(result)
case Failure(reason) => Future.failed(reason)
} onComplete {
case Success(result) => processResult()
case Failure(reason) => log.error(s"Couldn't do it: ${reason.getMessage}")
}
But that feels a bit redundant: creating a Future just to model the fact that something in the future (which already is captured inside a Future) went well.
This approach creates an extra Future which I hope to get rid of, but I can't figure out how. Any suggestions?
It's not clear to me why you don't just...
Future {
someMethodReturningTry() match {
case Success(result) => processResult(result)
case Failure(reason) => log.error(s"Couldn't do it: ${reason.getMessage}")
}
}
You're free to handle, or ignore, the Future failure separately.
You could adjust your pattern matching in the following way:
Future {
someMethodReturningTry()
} onComplete {
case Success(Success(result)) => processResult()
case Success(Failure(reason)) => log.error(s"Couldn't do it: ${reason.getMessage}")
case Failure(reason) =>
log.error(s"The future failed: ${reason.getMessage}")
// or do nothing
}
Note that the onSuccess callback is executed only if the Future succeeds, so your original code didn't do anything if the Future contained an exception. If that is your intent, you can leave the case Failure(reason) => clause above blank (but it's probably more helpful to retain the error logging as shown).
Related
I want to make some cleanup (like close db connection) after a Future is complete.
Currently I achieve it this way:
Future { ... } onComplete {
case Success(v) =>
// ...
conn.close()
case Failure(ex) =>
// ...
conn.close()
}
There's duplicate code and it's also tedious.
Is there any best practice to this?
Since the same action conn.close() is performed on both success and failure, consider executing it as a side-effect using andThen like so
Future { ... } andThen { _ => conn.close() }
Similarly, using onComplete we could do
Future { ... } onComplete { _ => conn.close() }
The difference between andThen and onComplete is that latter will return Unit, that is, discard the returned value of the Future.
The sad truth is that Scala Futures don't have this basic feature built in. I would therefore strongly suggest using a modern effect system like ZIO or cats-effect, both of which solve this problem and a myriad of others that Futures have. The easiest way to do what you want is to use the bracket method:
https://zio.dev/docs/overview/overview_handling_resources
Now bracket works great, but there's a way that usually works even better: the Managed type. It's virtually impossible to write code that leaks resources if you consistently use Managed when acquiring resources:
https://zio.dev/docs/datatypes/datatypes_managed
That said,if you absolutely must use Futures, you'll have to write your own try-finally equivalent. Or you can use mine:
def tryFinally[A](tryy: => Future[A])(finallyy: => Future[Any])(
implicit ec: ExecutionContext): Future[A] =
Future.fromTry(Try(tryy)).flatten.transformWith { t =>
finallyy.flatMap((_: Any) => Future.fromTry(t))
}
I like to use map, you can do something like this:
val mapped: Future[String] = future.map(_ => "OK").recover{case _ => "KO"}
I'm using http4s, and I have a Try that generates some json data for a response:
case GET -> Root / "something" =>
getSomethingTry() match {
case Success(something) => Ok(something)
case Failure(CustomNotFoundException(reason)) => NotFound(reason)
case Failure(CustomConflictException()) => Conflict()
}
This function correctly returns a Task[Response]
However, I want to replace the Try with a Future. Matching no longer works, because the future may not have been resolved at the time of the match. So, I can map the future:
case GET -> Root / "something" =>
getSomethingFuture().map {
something => Ok(something)
}.recover {
case CustomNotFoundException(reason) => NotFound(reason)
case CustomConflictException() => Conflict()
}
But this returns a Future[Task[Response]] which is not what http4s wants. It doesn't seem appropriate to use Await.result to unbox the Future - I think this could cause thread pool issues - but it does make the code work.
http4s accepts futures as the argument to the task creator:
case GET -> Root / "something" =>
Ok(getSomethingFuture())
But this doesn't let me set different status codes in the event of different errors. A solution could be to do a .recover on a task, but I can't see an obvious way to do that.
How can I call different http4s task wrappers in the event of different Future failure cases? Do I need to use middleware?
Assuming you're using http4s 0.17 and higher, your Task is fs2.Task.
It's easy to convert the Future to Task and then deal with the latter:
case GET -> Root / "something" =>
Task.fromFuture(getSomethingFuture())
.flatMap {
something => Ok(something)
}
.handleWith {
case CustomNotFoundException(reason) => NotFound(reason)
case CustomConflictException() => Conflict()
}
I'd recommend, however, to use Task throughout your program instead of Try or Future
You dont really need to unwrap the future. Play framework provides action.async for returning Future.
You can use it in the following way
Action.async {
getSomethingFuture().map {
something => Ok(something)
}.recover {
case CustomNotFoundException(reason) => NotFound(reason)
case CustomConflictException() => Conflict()
}
}
https://www.playframework.com/documentation/2.6.x/ScalaAsync#returning-futures
I am trying to get the following example working:
def asyncTest = Action {
val willBeInt = Future {
Thread.sleep(5000)
100
}
willBeInt.onComplete({
case Success(value) => Ok(s"Value = $value")
case Failure(e) => Failure(e)
})
}
But I am getting an error about overloading a method:
Overloaded method value [apply] cannot be applied to (Unit)
I'm coming from a background in NodeJS and am struggling to figure out how these callbacks are supposed to work while simultaneously returning a result to appease the method signature.
Think of Action as a function that returns a promise, rather than as a function that accepts a callback. In scala terminology, you'll be returning a Future. Play's internals will be calling onComplete (or something similar) on their own (analagous to a javascript Promise's then function).
Specifically, your compilation error is due to the fact that onComplete returns Unit, when the Action block is expecting you to return a Future. You can use map to transform your willBeInt future into what Play is looking for:
def asynTest = Action.async {
val willBeInt = Future {
Thread.sleep(5000)
100
}
// note you will probably need to
// import scala.concurrent.ExecutionContext.Implicits.global
// to be able to call `map` here
willBeInt map { value =>
Ok(s"Value = $value")
} recover {
case e: Throwable => InternalServerError(e.toString)
}
}
For some extra reading, check out the docs for Future, and the docs for Action
In scala, Futures have a sort of rescue function that takes a PartialFunction. This code is skipped if the Future resolved with a response but is called if a failure occurs.
I want to simple wrap the partial function in a proxy that always executes code that writes to a stat counter. At first I was thinking I would just create another PartialFunction but soon realized that does not work very well with the isDefined, then apply as I really want this to be called every time.
How do I go about proxying the PartialFunction such that my code is always called when the Future has an exception?
To summarize the comments: You can use the onFailure callback to execute some side-effecting code (logging) when a Future fails.
val future = Future(1 / 0)
future.onFailure {
case _ => println("I have seen the Future, and it doesn't look good.")
}
As #cmbaxter notes, you could also use andThen on the Future, which accepts a PartialFunction[Try[A], B] and returns the original Future. So you could apply the side-effecting function using andThen, and than recover afterwards. You could even chain them multiple times.
Future(1 / 0)
.andThen { case Failure(_) => println("Future failed.") }
.recover { case e: ArithmeticException => 0 }
.andThen { case Failure(_) => println("Tried to recover, and still failed.") }
Or a helper that always includes it:
object FutureLogger {
def apply[A](a: => A): Future[A] = Future(a).andThen {
case Failure(_) => println("FAILURE")
}
}
For the following snippet IDEA gives a warning, that future {None} would be redundant:
queryString match {
case Some(query) => ... // Do async call
case None => future { None }
}
Is there a better possibility to do it?
You can create an already set future without spawning a closure, using Future.successful[T](result: T), so maybe Future.successful(None) is what you want.
Since futures already distinguish between success and failure independent of their type parameter, however, you could signalise a failure also through Future.failed(new Exception("No query string")), given that your async call can also omit the wrapping in Some.
I don't know if IDEA's warning is helpful in this case.
You could potentially silence it by pushing the matching down into the future:
future {
queryString match {
case Some(query) => Some(computeResult(query))
case None => None
}
}
(or more briefly: future { queryString.map(computeResult(_)) })
I think i solved it by myself: I don't need an Option encapsulated in my Future, because the Future itself can also fail and therefore an failure is kind of equal to a None.
Now i only return a Future and:
queryString match {
case Some(query) =>
//async Call which returns a Future without Option
case None => throw new Exception("Error")
}