I'm learning scala and I want to run 3 futures asynchronously and get an answer for each one. But I can't get it to work.
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.{Success, Failure}
import scala.concurrent.ExecutionContext.Implicits.global
def f_Fail(): Future[String] = Future {
Thread.sleep(1000)
throw new RuntimeException("わざと失敗")
}
def f_success(): Future[String] = Future {
Thread.sleep(1000)
"future!"
}
val f1 = f_success()
val f2 = f_success()
val f3 = f_Fail()
val future =for {
n <- f1
m <- f2
p <- f3
} yield n "\n" m "\n" p
Await.ready(future,Duration.Inf).value.get match {
case Success(v) => println(v)
case Failure(e) => println("not found")
}
Expected results
"future!"
"future!"
"not found"
Actual result
"not found"
CodePudding user response:
A for
-comprehension is so-called "syntactic sugar" for nested flatMap
calls and finally a map
. In your case, the for
-comprehension can be effectively re-written as:
val future = f1.flatMap(n => f2.flatMap(m => f3.map(p => n "\n" m "\n" p)))
In the context of Future
s, the flatMap
method requires the Future
upon which its called to be completed before the result is available for the following step. Simplifying for clarity, consider the following:
f1.flatMap(n => f2.map(m => m n))
In order for the lamdba passed to the flatMap
method to be invoked, the result of the calculation must be known.
Effectively, flatMap
calls represent the concept of a direct dependency on a previous computation being executed successfully. In the same way, its "sugared" counterpart, the for
-comprehension, represents the same.
This means that if any step of the chain fails, the overall result will be failed, which is what you are experiencing in your example.
What you can do is the following:
val f1 = f_success().recover(_ => "not found")
val f2 = f_success().recover(_ => "not found")
val f3 = f_Fail().recover(_ => "not found")
for {
n <- f1
m <- f2
p <- f3
} {
println(n)
println(m)
println(p)
}
Notice that:
Future
s are "eager", i.e. the computation is started immediately when theFuture
itself is instantiated. This means that the first three lines effectively start each computation independently and (given enough threads and resources) concurrently or even in parallel.- each
Future
defines how to be recovered by returning the"not found"
string in case of error (which is the case forf3
). - the side effect (
println
) is performed as part of thefor
-comprehension itself, allowing you to avoid blocking on the synchronous context -- in this case, thefor
-comprehension withoutyield
is equivalent to executing the final step inside aforeach
instead of amap
, which is better suited to express side effects.
Now, let's say that you want to start your Future
s lazily by simply wrapping their definition in a function. You'll notice that suddenly the execution doesn't necessarily take advantage of multiple threads and cores and takes 3 seconds instead of one, as in the following example:
def f1 = f_success().recover(_ => "not found")
def f2 = f_success().recover(_ => "not found")
def f3 = f_Fail().recover(_ => "not found")
for {
n <- f1
m <- f2
p <- f3
} {
println(n)
println(m)
println(p)
}
This is because of the semantics of flatMap
. You can solve this problem by using a construct that doesn't imply some form of direct dependency between the steps of the calculation, like zip
:
def f1 = f_success().recover(_ => "not found")
def f2 = f_success().recover(_ => "not found")
def f3 = f_Fail().recover(_ => "not found")
for (((n, m), p) <- f1.zip(f2).zip(f3)) {
println(n)
println(m)
println(p)
}
This runs again in ~1 second as you might expect.
Alternatively, if you want to still return the result as a Future
, you can of course use yield
as follows:
val future = for (((n, m), p) <- f1.zip(f2).zip(f3)) yield s"$n\n$m\n$p"
You can read more about for
-comprehensions here on the Scala book.